对于一个前端开发者来说,很少用到 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 = parent.prototype ; var o = new F (); for (var k in o) { flag = k in child.prototype ; if (!flag) { child.prototype [k] = o[k]; } } }
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 ('推特治国' );