本文讨论Typescript中的Class同ES5构造函数的对应关系,涉及TypeScript的诸多语法、构造函数、面向对象以及原型对象等相关知识点细节,本文只简单对比并不进行深入展开。

TypeScript 是JavaScript的超集,包含ES5、ES6、ES7+…和扩展。

我们知道在TS中,Class并不是什么新鲜的东西,它的本质其实就是ES那套构造函数·原型对象·实例对象的老古董。在下面的篇幅中,我会简单的对比Class的ES5写法,从最简单到复杂。

最简单的类

最简单的类,除名字外其它一无所有。

1
2
/* 文件名:01.ts_class_and_es5.ts */
class Class_test{}

我们通过tsc 01.ts_class_and_es5.ts命令对该文件进行编译,得到的是下面的JavaScript代码。

1
2
3
4
5
/* 文件名:01.ts_class_and_es5.js */
var Class_test = /** @class */ (function () {
function Class_test() {}
return Class_test;
}());

从编译后的结果可以看到对应的代码就是个空构造函数,精巧的地方在于这个函数被放到一个自调用函数中并返回,这里使用了一个同名的变量来接收内部的构造函数,多么经典的闭包应用啊。

+ 属性

我们尝试在上面代码的基础上,在这个类中加入属性的概念。

1
2
3
4
5
6
7
8
class Class_test {
name:string;
age:number;
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
}

我们为Class_test类新添加了属性的概念( 分别是 nameage ) , 这里其实如果不想写constructor构造函数的话其实可以给 nameage 设置一个初始值也是没问题的。

1
2
3
4
5
6
7
var Class_test = /** @class */ (function () {
function Class_test(name, age) {
this.name = name;
this.age = age;
}
return Class_test;
}());

可以发现写法跟ES5构造函数的写法一致,注意Class成员的几个修饰符:public protected private 在ES5代码的结构中没有意义,这里不做额外说明。

+ 方法(静态)

我们在上面代码的基础上再加上方法、静态属性、静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 设计类 */
class Class_test {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
getInfo():void{
console.log(`Name === ${this.name} Age === ${this.age}`);
}
/* 静态属性:构造函数(类)自己的属性,访问示例:类名.静态属性; */
/* 静态方法:构造函数(类)自己的方法,访问示例:类名.静态方法名称(); */
static className: string = "Class_test";
static getClassName():void{
console.log("ClassName === "+this.className); //this=>Class_Test
}
}

/* 实例化对象 */
let c:Class_test = new Class_test("文顶顶",18);
console.log(c.name,c.age);
c.getInfo();
console.log(Class_test.className);
Class_test.getClassName();

我们为代码添加了静态属性 className、静态方法 getClassName() 、实例方法 getInfo(),通过查看对应的ES5代码,我们会发现这也没什么特别的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Class_test = /** @class */ (function () {
function Class_test(name, age) {
this.name = name;
this.age = age;
}
Class_test.prototype.getInfo = function () {
console.log("Name === " + this.name + " Age === " + this.age);
};
Class_test.getClassName = function () {
console.log("ClassName === " + this.className); //this=>Class_Test
};
/* 静态属性:构造函数(类)自己的属性,访问示例:类名.静态属性; */
/* 静态方法:构造函数(类)自己的方法,访问示例:类名.静态方法名称(); */
Class_test.className = "Class_test";
return Class_test;
}());
var c = new Class_test("文顶顶", 18);
console.log(c.name, c.age); /* 文顶顶 18 */
c.getInfo(); /* Name === 文顶顶 Age === 18 */
console.log(Class_test.className); /* Class_test */
Class_test.getClassName(); /* ClassName === Class_test */

关键在于搞清楚成员原型成员静态成员实例成员构造函数原型对象以及实例化的关系。

+ 继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* 设计类(父类) */
class Class_super {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
getInfo():void{
console.log(`Name === ${this.name} Age === ${this.age}`);
}
static className: string = "Class_test";
static getClassName():void{
console.log("ClassName === "+this.className); //this=>Class_Test
}
}

/* 设计类(子类) */
class Class_child extends Class_super{
money:number;
constructor(name: string, age: number,money:number) {
super(name,age);
this.money = money;
}
getMoney():void{
console.log(`Money === ${this.money}`)
}
static child_static_func():void{
console.log("测试该方法能否被Class_super方法调用?No")
}
}

/* 实例化(对象) */
let child:Class_child = new Class_child("zs",19,100);
console.log(child,child.name,child.age,child.money);
child.getInfo();
child.getMoney();
console.log(Class_child.className);
Class_child.getClassName();
Class_child.child_static_func();
// Class_super.child_static_func(); 错误的示范

如果我们实现了类的继承,那么问题就会变得复杂得多,至少看起来如此。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; })
|| function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]};
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();

/* 设计类(父类) */
var Class_super = /** @class */ (function () {
function Class_super(name, age) {
this.name = name;
this.age = age;
}
Class_super.prototype.getInfo = function () {
console.log("Name === " + this.name + " Age === " + this.age);
};
Class_super.getClassName = function () {
console.log("ClassName === " + this.className); //this=>Class_Test
};
Class_super.className = "Class_test";
return Class_super;
}());
/* 设计类(子类) */
var Class_child = /** @class */ (function (_super) {
__extends(Class_child, _super);
function Class_child(name, age, money) {
var _this = _super.call(this, name, age) || this;
_this.money = money;
return _this;
}
Class_child.prototype.getMoney = function () {
console.log("Money === " + this.money);
};
Class_child.child_static_func = function () {
console.log("测试该方法能否被Class_super方法调用?No");
};
return Class_child;
}(Class_super));
/* 实例化(对象) */
var child = new Class_child("zs", 19, 100);
console.log(child, child.name, child.age, child.money);
/* Class_child { name: 'zs', age: 19, money: 100 } 'zs' 19 100 */
child.getInfo(); /* Name === zs Age === 19 */
child.getMoney(); /* Money === 100 */
console.log(Class_child.className); /* Class_test */
Class_child.getClassName(); /* ClassName === Class_test */
Class_child.child_static_func(); /* 测试该方法能否被Class_super方法调用?No */
// Class_super.child_static_func(); 错误的示范

蒽,是的。看上去,TS ( ES6 ) 中感觉Class继承的实现稍显复杂。接下来,我们试着挑出关键的部分,并把比较重要(表面感觉不太看得懂)的那部分代码加上些注释。

关键 ①

1
var _this = _super.call(this, name, age) || this;

这行代码的目的是获取父类的实例成员,具体采用的技术手段是通过借用构造函数调用( 在子构造函数中以call方法来调用父构造函数,并绑定this )的方式来获取。此处,拿到的是name age两个属性(当我们通过子类来实例化创建对象的时候,得到的实例化对象中因此拥有了nameage 属性)。

关键 ②

1
__extends(Class_child, _super);

这行代码是一个函数调用,其中__extends是函数名称,而Class_child, _super是传递给函数的具体实参。
该函数的作用是,通过特定的方式来获取父类(构造函数)的静态成员(包括静态属性以及静态方法)并让实例对象可以通过原型链来访问父类原型对象上面的成员(属性和方法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* @Description: __extends主要用于处理(静态成员)的继承
* @return: 函数形态 function f(_child,_super){} */
var __extends = (this && this.__extends) || (function() {
/* @Description: extendStatics用于扩展静态成员(静态属性和静态方法)
* param __child 子类(Class_child)
* param __super 父类(Class_super)*/
var extendStatics = function(__child, __super) {
/* 1.确定函数 */
/* extendStatics变量的值总是为一个函数,该函数的目的是_super的属性和方法(静态)*/
/* 核心策略 */
/* ① 尝试直接使用 Object.setPrototypeOf函数 类似于__child.__proto__ = __super */
/* ② 直接通过__child.__proto__ = __super来设置 */
/* ③ 通过遍历的方式来拷贝_super对象的实例成员 */
extendStatics = Object.setPrototypeOf
||({ __proto__: [] } instanceof Array
&& function(__child, __super) {
__child.__proto__ = __super;
})
||function(d, b) {
for (var p in b) {
if (b.hasOwnProperty(p)) d[p] = b[p]; }
};
/* 2.调用函数(完成继承) */
return extendStatics(__child, __super);
};
return function(_child, _super) {
/* [1] 完成对父类(构造函数)静态成员(属性+方法)的拷贝工作 */
extendStatics(_child, _super);

/* [2] 完成对父类(构造函数)原型成员(主要是方法)的原型链继承 */
/* 内部的this取决于__()函数如何调用,使用new调用则this指向的是内部新创建的实例对象 */
function __() { this.constructor = _child; }

/* 如果_spuer为null,那么就创建一个新对象*/
/* 设置该对象的原型对象为_super[let o = {}; o.__prototype = _super] */
/* 并把最终结果赋值给_child.prototype, 否则就间接通过__()这个函数来设置原型对象*/
/* 并创建实例化对象,并设置 _child.prototype = new __() */
_child.prototype =
_super === null ?
Object.create(_super) :
(__.prototype = _super.prototype, new __());
};
})();

我在代码中穿插并加上了一些说明性的注释 ,看上去似乎比较复杂,稍微总结下:

1
2
3
4
5
6
__extends() 函数做了两件事情:
[1] 完成对父类(构造函数)静态成员(属性+方法)的拷贝工作
[2] 完成对父类(构造函数)原型成员(主要是方法)的原型链继承
如何做的?
[1] child.__proto__ = super
[2] child.prototype = super.prototype; + 解决共享问题(中间加了间隔层)

+ Interface

接口( interface )是 typescript 中的重要概念,而且接口也能继承且可以约束类,因此这里简单的比较下,下面给出示例代码以及对应的JavaScript代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 1.定义接口(父) */
interface Test_Class_Super_Interface {
name: string;
age: number;
getName: () => void;
getAge: () => void;
}

/* 2.定义接口(子) */
interface Test_Class_Child_Interface extends Test_Class_Super_Interface{
id:number;
getId:()=>void;
}

/* 3.设计类:该类实现指定的接口 */
class Test_Class implements Test_Class_Child_Interface {
id: number = 0;
name: string;
age: number;
getName(): void {
console.log("getName() " + this.name)
};
getAge(): void {
console.log("getAge() " + this.age)
};
getId():void{
console.log(this.id);
}
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
/* 4.创建实例化对象 */
let instance: Test_Class = new Test_Class("ls", 18);
console.log(instance);

上面代码设计了两个接口,分别是Test_Class_Super_InterfaceTest_Class_Child_Interface,它们是继承关系,Test_Class 类实现了Test_Class_Child_Interface接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Test_Class = /** @class */ (function() {
function Test_Class(name, age) {
this.id = 0;
this.name = name;
this.age = age;
}
Test_Class.prototype.getName = function() {
console.log("getName() " + this.name);
};;
Test_Class.prototype.getAge = function() {
console.log("getAge() " + this.age);
};;
Test_Class.prototype.getId = function() {
console.log(this.id);
};
return Test_Class;
}());
var instance = new Test_Class("ls", 18);
console.log(instance);

我们发现转换后 JavaScript版本的代码里面好像没有任何与接口相关的信息,接口是 typescript 中特有的类型, 在编译后会自动消失。