前端开发系列130-进阶篇之TS、Class and ES5
TypeScript 是JavaScript的超集,包含ES5、ES6、ES7+...和扩展。
我们知道在TS中,Class并不是什么新鲜的东西,它的本质其实就是ES那套构造函数·原型对象·实例对象
的老古董。在下面的篇幅中,我会简单的对比Class的ES5写法,从最简单到复杂。
最简单的类
最简单的类,除名字外其它一无所有。
/* 文件名:01.ts_class_and_es5.ts */
class Class_test{}
我们通过tsc 01.ts_class_and_es5.ts
命令对该文件进行编译,得到的是下面的JavaScript代码。
/* 文件名:01.ts_class_and_es5.js */
var Class_test = /** @class */ (function () {
function Class_test() {}
return Class_test;
}());
从编译后的结果可以看到对应的代码就是个空构造函数,精巧的地方在于这个函数被放到一个自调用函数中并返回,这里使用了一个同名的变量来接收内部的构造函数,多么经典的闭包应用啊。
+ 属性
我们尝试在上面代码的基础上,在这个类中加入属性的概念。
class Class_test {
name:string;
age:number;
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
}
我们为Class_test类新添加了属性的概念( 分别是 name
和 age
) , 这里其实如果不想写constructor构造函数的话其实可以给 name
和 age
设置一个初始值也是没问题的。
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代码的结构中没有意义,这里不做额外说明。
+ 方法(静态)
我们在上面代码的基础上再加上方法、静态属性、静态方法。
/* 设计类 */
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代码,我们会发现这也没什么特别的。
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 */
关键在于搞清楚成员、原型成员、静态成员、实例成员、构造函数、原型对象以及实例化的关系。
+ 继承
/* 设计类(父类) */
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(); 错误的示范
如果我们实现了类的继承,那么问题就会变得复杂得多,至少看起来如此。
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继承的实现稍显复杂。接下来,我们试着挑出关键的部分,并把比较重要(表面感觉不太看得懂)的那部分代码加上些注释。
关键 ①
var _this = _super.call(this, name, age) || this;
这行代码的目的是获取父类的实例成员,具体采用的技术手段是通过借用构造函数调用( 在子构造函数中以call方法来调用父构造函数,并绑定this )的方式来获取。此处,拿到的是name
age
两个属性(当我们通过子类来实例化创建对象的时候,得到的实例化对象中因此拥有了name
和 age
属性)。
关键 ②
__extends(Class_child, _super);
这行代码是一个函数调用,其中__extends是函数名称,而Class_child, _super是传递给函数的具体实参。
该函数的作用是,通过特定的方式来获取父类(构造函数)的静态成员(包括静态属性以及静态方法)并让实例对象可以通过原型链来访问父类原型对象上面的成员(属性和方法)。
/* @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 __());
};
})();
我在代码中穿插并加上了一些说明性的注释 ,看上去似乎比较复杂,稍微总结下:
__extends() 函数做了两件事情:
[1] 完成对父类(构造函数)静态成员(属性+方法)的拷贝工作
[2] 完成对父类(构造函数)原型成员(主要是方法)的原型链继承
如何做的?
[1] child.__proto__ = super
[2] child.prototype = super.prototype; + 解决共享问题(中间加了间隔层)
+ Interface
接口( interface )是 typescript 中的重要概念,而且接口也能继承且可以约束类,因此这里简单的比较下,下面给出示例代码以及对应的JavaScript代码。
/* 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_Interface
和 Test_Class_Child_Interface
,它们是继承关系,Test_Class 类实现了Test_Class_Child_Interface
接口。
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 中特有的类型
, 在编译后会自动消失。