对于一个前端开发者来说,很少用到 class ,因为在 JavaScript 中更多的是 函数式 编程,抬手就是一个 function,几乎不见 class 或 new 的踪影。所以 设计模式 也是大多数前端开发者的一个短板。
最近在学习 Angular  的过程中发现其大量的运用了 class,不得不佩服,Angular 确实是一个优秀的、值得深入研究的 框架 。
本文将简单的介绍一下 JavaScript 和 TypeScript 中的 class。
基本概念 在介绍 class 之前,要先介绍一些基本的概念。
静态成员
 类自身的成员,可以继承,但实例无法访问,一般多见于工具类,比如在jQuery时代最常见的 $.ajax ,ajax 便是 $ 的静态方法,使用方便,不需要再通过 new 或者函数调用的得到一个新实例。
私有成员
 类内部的成员,一般是不能继承的,只能在内部使用,实例无法访问,有一点点像闭包内部的变量,但是还是一定的差别,目前 JavaScript 无法直接定义私有成员,只能通过其它方式辅助实现。
getter/setter
 存取器属性,当我们访问或者修改一个实例的属性的时候,我们可通过存取器属性拦截这两个操作,从而做一些其它的事情,vue正是通过这个api来实现对数据变化的追踪。
实例成员
 指 new 出来的实例所具有的成员,可以被继承,也是通过这个特性实现了代码的复用。
抽象类,抽象方法
 抽象类指不可以被实例化的类,通过 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 class  Person  {                                            static  foo (         console .log (`类 ${this .name}  有一个 ${this .x} ` );     }     constructor (name, age, gender ) {         this .name  = name;         this .age  = age;         this .gender  = gender;     }          get  fullName () {         const  suffix = this .gender  === '男'  ? '先生'  : '女士' ;         return  this .name  + suffix;     }     set  fullName (value ) {         console .log (`你已改名为 ${value}  ` );     }          sayHello (         console .log (`你好我是 ${this .fullName}  ,我 ${this .age}  岁了` );     } } Person .x  = '静态属性x' ;
class  Chinese  extends  Person  {    static  bar (         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}  ` );     } } 
class  American  extends  Person  {         static  bar (         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 ;       Person .foo ();   Chinese .x ;      Chinese .foo ();  Chinese .bar ();  American .x ;     American .y ;     American .foo (); American .bar (); const  p = new  Person ('Lucy' , 20 , '女' );const  c = new  Chinese ('韩梅梅' , 18 , '女' , '咏春拳' );const  a = new  American ('特朗普' , 72 , '男' , 'Donald J. Trump' );c.sayHello ();    c.martial ();     a.sayHello ();    a.sendTwitter ('推特治国' );   
ES6 之前的 class ES5 的继承,实质是先创造子类的实例对象 this,
然后再将父类的方法添加到 this 上面 Parent.apply(this) 。
ES6 的继承机制完全不同,实质是先创造父类的实例对象 this,所以必须先调用 super 方法,
然后再用子类的构造函数修改this。
为了实现继承,我们需要先实现一个 extendsClass 函数,它的作用是让子类继承父类的静态成员和实例成员。
function  extendsClass (parent, child ) {         var  flag = false ;          for  (var  k in  parent) {         flag = k in  child;         if  (!flag) {             child[k] = parent[k];         }     }               var  F = function  (     F.prototype prototype      var  o = new  F ();     for  (var  k in  o) {         flag = k in  child.prototype          if  (!flag) {             child.prototype          }     } } 
function  Person (name, age, gender ) {    this .name  = name;     this .age  = age;     this .gender  = this .gender ;          Object .defineProperty (this , 'fullName' , {         get : function  (             var  suffix = this .gender  === '男'  ? '先生'  : '女士' ;             return  this .name  + suffix;         },         set : function  (             console .log ('你已改名为 '  + value + ' ' );         },     }); } Person .x  = '静态属性x' ;Person .foo  = function  (    console .log ('类 '  + this .name  + ' 有一个 '  + this .x ); } Person .prototype     constructor : Person ,               sayHello : function  (         console .log ('你好我是 '  + this .fullName  + ' ,我 '  + this .age  + ' 了' );     }, }; 
function  Chinese (name, age, gender, kungfu ) {         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 );
function  American (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 方法是抽象方法,因为每个国家打招呼的方式不一样。另外一个人的性别是只能读取,不能修改的,且是确定的是,不是男生就是女生,所以还要借助一下枚举。
enum Gender  {     female = 0 ,     male = 1  }; 
abstract class  Person  {     private x : string = '私有属性x,子类和实例都无法访问' ;     protected y : string = '私有属性y,子类可以访问,实例无法访问' ;     name : string;     public age : number;     public readonly gender : Gender ;      public static  x : string = '静态属性x' ;     public static  foo (         console .log (`类 ${this .name}  有一个 ${this .x} ` );     }     constructor (name: string, age: number, gender: Gender ) {         this .name  = name;         this .age  = age;         this .gender  = gender;     }     get  fullName (): string {         const  suffix = this .gender  === 1  ? '先生'  : '女士' ;         return  this .name  + suffix;     }     set  FullName (value: string ) {         console .log (`你已改名为 ${value}  ` );     }          abstract sayHello (): void ; } 
class  Chinese  extends  Person  {    public kungfu : string;     public static  bar (         console .log (`类 ${this .name}  的父类是 ${super .name} ` );         super .foo ();     }     public constructor (name: string, age: number, gender: Gender, kungfu: string ) {         super (name, age, gender);         this .kungfu  = kungfu;     }     public sayHello (): void  {         console .log (`你好我是 ${this .fullName}  ,我 ${this .age}  岁了` );     }     public martial (         console .log (`${this .name}  正在修炼 ${this .kungfu}  ` );     } } 
class  American  extends  Person  {    static  y = '静态属性y' ;     public static  bar (         console .log (`类 ${this .name}  有自己的 ${this .y}  ,还继承了父类 ${super .name}  的 ${super .x} ` );     }     public twitter : string;     public constructor (name: string, age: number, gender: Gender, twitter: string ) {         super (name, age, gender);         this .twitter  = twitter;     }     public sayHello (): void  {         console .log (`Hello, I am ${this .fullName}  , I'm ${this .age}  years old` );     }     public sendTwitter (msg : string): void  {         console .log (`${this .name}  : ` );         console .log (`  ${msg} ` );     } } 
Person .x ;       Person .foo ();   Chinese .x ;      Chinese .foo ();  Chinese .bar ();  American .x ;     American .y ;     American .foo (); American .bar (); const  c : Chinese  = new  Chinese ('韩梅梅' , 18 , Gender .female , '咏春拳' );const  a : American  = new  American ('特朗普' , 72 , Gender .male , 'Donald J. Trump' );c.sayHello ();    c.martial ();     a.sayHello ();    a.sendTwitter ('推特治国' );