深入理解JavaScript和TypeScript中的class
前言
对于一个前端开发者来说,很少用到class,因为在JavaScript中更多的是函数式编程,抬手就是一个function,几乎不见class或new的踪影。所以设计模式也是大多数前端开发者的一个短板。
最近在学习Angular的过程中发现其大量的运用了class,不得不佩服,Angular确实是一个优秀的、值得深入研究的框架。
本文将简单的介绍一下JavaScript和TypeScript中的class。
基本概念
在介绍class之前,要先介绍一些基本的概念。
1、静态成员
类自身的成员,可以继承,但实例无法访问,一般多见于工具类,比如在jQuery时代最常见的$.ajax,ajax便是$的静态方法,使用方便,不需要再通过new或者函数调用的得到一个新实例。
2、私有成员
类内部的成员,一般是不能继承的,只能在内部使用,实例无法访问,有一点点像闭包内部的变量,但是还是一定的差别,目前JavaScript无法直接定义私有成员,只能通过其它方式辅助实现。
3、getter/setter
存取器属性,当我们访问或者修改一个实例的属性的时候,我们可通过存取器属性拦截这两个操作,从而做一些其它的事情,vue正是通过这个api来实现对数据变化的追踪。
4、实例成员
指new出来的实例所具有的成员,可以被继承,也是通过这个特性实现了代码的复用。
5、抽象类,抽象方法
抽象类指不可以被实例化的类,通过new关键字调用会报错,一般都被设计成父类。
抽象方法,只提供方法的名称,参数和返回值,不负责实现,具体的实现由子类去完成,如果一个子类继承于抽象类,那么这个子类必须实现父类所有的抽象方法,否则会报错。
这两个概念在JavaScript都无法直接实现,但在TypeScript或其它面向对象语言中可以轻松实现,另外这个特性也是用于实现多态的重要手段。
案例介绍
为了更好的介绍class,本文将采用三个类来做例子,分别是Person、Chinese、American。从字面上可以很快的知道:Person是父类(基类),Chinese和American是子类(派生类)。
Person有name、age、gender三个属性,sayHello方法和fullName存取器属性。同时Person还有一些静态成员和私有成员,由于实在太难想例子了,所以就用foo、bar、x、y、z这些来代替吧。
作为子类的Chinese和American继承了Person的实例成员和静态成员。同时它们自身也有一些自己的方法和属性:
Chinese有kungfu属性,会习武martial。
American有twitter,还可以sendTwitter。
接下来我们就分别使用JavaScript和TypeScript来实现这个案例。
JavaScript中的class
JavaScript中的class要分开说,在ES6中提供了两个关键字class和extends,虽然它们只是语法糖,底层还是再利用prototype实现继承的,但是不能否认,这中写法确实让代码更清晰,更易读。
ES6中的class
classPerson{
//#x='私有属性x';
//staticx='静态属性x';
//name;
//age;
//gender;
//上面的写法还在提案中,并没有成为正式标准,不过变化的可能性已经不大了。
//顺便吐槽一下,用#表示私有成员,真的是很无语.
/**
*Person的静态方法,可以被子类继承
*可以通过this访问静态成员
*/
staticfoo(){
console.log(`类${this.name}有一个${this.x}`);
}
constructor(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
}
/**
*数据存储器,可以访问实例成员,子类的实例可以继承
*以通过this访问实例成员
*/
getfullName(){
constsuffix=this.gender==='男'?'先生':'女士';
returnthis.name+suffix;
}
setfullName(value){
console.log(`你已改名为${value}`);
}
/**
*Person的实例方法,可以被子类的实例继承
*可以通过this访问实例成员
*/
sayHello(){
console.log(`你好我是${this.fullName},我${this.age}岁了`);
}
}
Person.x='静态属性x';
classChineseextendsPerson{
staticbar(){
console.log(`类${this.name}的父类是${super.name}`);
super.foo();
}
constructor(name,age,gender,kungfu){
super(name,age,gender);
this.kungfu=kungfu;
}
martial(){
console.log(`${this.name}正在修炼${this.kungfu}`);
}
}
classAmericanextendsPerson{
//staticy='静态属性y';
staticbar(){
console.log(`类${this.name}有自己的${this.y},还继承了父类${super.name}的${super.x}`);
}
constructor(name,age,gender,twitter){
super(name,age,gender);
this.twitter=twitter;
}
sendTwitter(msg){
console.log(`${this.name}:`);
console.log(`${msg}`);
}
}
American.y='静态属性y';
Person.x;//静态属性x
Person.foo();//类Person有一个静态属性x
Chinese.x;//静态属性x
Chinese.foo();//类Chinese有一个静态属性x
Chinese.bar();//类Chinese的父类是Person
American.x;//静态属性x
American.y;//'静态属性y
American.foo();//类American有一个静态属性x
American.bar();//类American有自己的静态属性y,还继承了父类Person的静态属性x
constp=newPerson('Lucy',20,'女');
constc=newChinese('韩梅梅',18,'女','咏春拳');
consta=newAmerican('特朗普',72,'男','DonaldJ.Trump');
c.sayHello();//你好我是韩梅梅女士,我18岁了
c.martial();//韩梅梅正在修炼咏春拳
a.sayHello();//你好我是特朗普先生,我72岁了
a.sendTwitter('推特治国');//特朗普:推特治国
ES6之前的class
ES5的继承,实质是先创造子类的实例对象this,
然后再将父类的方法添加到this上面Parent.apply(this)。
ES6的继承机制完全不同,实质是先创造父类的实例对象this,所以必须先调用super方法,
然后再用子类的构造函数修改this。
为了实现继承,我们需要先实现一个extendsClass函数,它的作用是让子类继承父类的静态成员和实例成员。
functionextendsClass(parent,child){
//防止子类和父类相同名称的成员被父类覆盖
varflag=false;
//继承静态成员
for(varkinparent){
flag=kinchild;
if(!flag){
child[k]=parent[k];
}
}
//继承父类prototype上的成员
//用一个新的构造函数切断父类和子类之间的数据共享
varF=function(){}
F.prototype=parent.prototype;
varo=newF();
for(varkino){
flag=kinchild.prototype;
if(!flag){
child.prototype[k]=o[k];
}
}
}
functionPerson(name,age,gender){
this.name=name;
this.age=age;
this.gender=this.gender;
//如果将getter/setter写在prototype会获取不到
Object.defineProperty(this,'fullName',{
get:function(){
varsuffix=this.gender==='男'?'先生':'女士';
returnthis.name+suffix;
},
set:function(){
console.log('你已改名为'+value+'');
},
});
}
Person.x='静态属性x';
Person.foo=function(){
console.log('类'+this.name+'有一个'+this.x);
}
Person.prototype={
constructor:Person,
//getfullName(){},
//setfullName(value){},
sayHello:function(){
console.log('你好我是'+this.fullName+',我'+this.age+'了');
},
};
functionChinese(name,age,gender,kungfu){
//用call改变this指向,实现继承父类的实例属性
Person.call(this,name,age,gender);
this.kungfu=kungfu;
}
Chinese.bar=function(){
console.log('类'+this.name+'的父类是'+Person.name);
Person.foo();
}
Chinese.prototype={
constructor:Chinese,
martial:function(){
console.log(this.name+'正在修炼'+this.kungfu+'');
}
};
extendsClass(Person,Chinese);
functionAmerican(name,age,gender,twitter){
Person.call(this,name,age,gender);
this.twitter=twitter;
}
American.y='静态属性y';
American.bar=function(){
console.log('类'+this.name+'有自己的'+this.y+',还继承了父类'+Person.name+'的'+Person.x);
}
American.prototype={
constructor:American,
sendTwitter:function(msg){
console.log(this.name+':');
console.log(''+msg);
}
};
extendsClass(Person,American);
TypeScript中的class
讲完了JavaScript中的类,还是没有用到抽象类,抽象方法,私有方法这三个概念,由于JavaScript语言的局限性,想要实现这三种概念是很困难的,但是在TypeScript可以轻松的实现这一特性。
首先我们稍微修改一下例子中的描述,Person是抽象类,因为一个正常的人肯定是有国籍的,Person的sayHello方法是抽象方法,因为每个国家打招呼的方式不一样。另外一个人的性别是只能读取,不能修改的,且是确定的是,不是男生就是女生,所以还要借助一下枚举。
enumGender{
female=0,
male=1
};
abstractclassPerson{
privatex:string='私有属性x,子类和实例都无法访问';
protectedy:string='私有属性y,子类可以访问,实例无法访问';
name:string;
publicage:number;
publicreadonlygender:Gender;//用关键字readonly表明这是一个只读属性
publicstaticx:string='静态属性x';
publicstaticfoo(){
console.log(`类${this.name}有一个${this.x}`);
}
constructor(name:string,age:number,gender:Gender){
this.name=name;
this.age=age;
this.gender=gender;
}
getfullName():string{
constsuffix=this.gender===1?'先生':'女士';
returnthis.name+suffix;
}
setFullName(value:string){
console.log(`你已改名为${value}`);
}
//抽象方法,具体实现交由子类完成
abstractsayHello():void;
}
classChineseextendsPerson{
publickungfu:string;
publicstaticbar(){
console.log(`类${this.name}的父类是${super.name}`);
super.foo();
}
publicconstructor(name:string,age:number,gender:Gender,kungfu:string){
super(name,age,gender);
this.kungfu=kungfu;
}
publicsayHello():void{
console.log(`你好我是${this.fullName},我${this.age}岁了`);
}
publicmartial(){
console.log(`${this.name}正在修炼${this.kungfu}`);
}
}
classAmericanextendsPerson{
staticy='静态属性y';
publicstaticbar(){
console.log(`类${this.name}有自己的${this.y},还继承了父类${super.name}的${super.x}`);
}
publictwitter:string;
publicconstructor(name:string,age:number,gender:Gender,twitter:string){
super(name,age,gender);
this.twitter=twitter;
}
publicsayHello():void{
console.log(`Hello,Iam${this.fullName},I'm${this.age}yearsold`);
}
publicsendTwitter(msg:string):void{
console.log(`${this.name}:`);
console.log(`${msg}`);
}
}
Person.x;//静态属性x
Person.foo();//类Person有一个静态属性x
Chinese.x;//静态属性x
Chinese.foo();//类Chinese有一个静态属性x
Chinese.bar();//类Chinese的父类是Person
American.x;//静态属性x
American.y;//'静态属性y
American.foo();//类American有一个静态属性x
American.bar();//类American有自己的静态属性y,还继承了父类Person的静态属性x
constc:Chinese=newChinese('韩梅梅',18,Gender.female,'咏春拳');
consta:American=newAmerican('特朗普',72,Gender.male,'DonaldJ.Trump');
c.sayHello();//你好我是韩梅梅女士,我18岁了
c.martial();//韩梅梅正在修炼咏春拳
a.sayHello();//Hello,Iam特朗普先生,I'm72yearsold
a.sendTwitter('推特治国');//特朗普:推特治国
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。