typescript 的 polyfill 学习1-Class 继承篇

Class 继承

js 是多范式的编程语言,同样也是支持面向对象编程的,类 是面向对象中是很重要的概念。

区别于传统的java,c#基于模板的类,js是基于原型的。

类继承一般是通过原型链的方式来实现,在es3时代,可以使用Base.js这个库来进行类编程。

而ES6通过关键字class来定义类,这种语法糖让写法更加清晰,更像传统的类编程,也因此屏蔽了原型链的细节,会减少初学者的困惑,不过也因为这样就失去了理解js本身的语法特性的机会。

下面用ts写的两个类,我们看看转成es5后,js是怎么模拟这个 语法糖的。

代码使用amd进行模块封装:

{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": false,
"module": "amd",
"target": "es5",
"jsx": "react"
},
"include": [
"./src/**/*"
]
}

ts代码:

 

//基类
export class A { public Pro1:string = "pro1";//公共成员
private fPro1:string = "fPro1";//私有成员 //私有方法
private fMethod1():string{
return "123";
}
//保护方法(可继承的方法)
protected pMethod1():string{
return "456";
}
//公共方法
public Method1(){ } } //子类B
export class B extends A { public Pro2:string ="Pro2";
private fPro2:string ="fPro2"; constructor(para:string){
super(); //构造器重载
this.fPro2 = para ;
} public Method2(){ }
//方法重载
protected pMethod1():string{
return super.pMethod1();
}
private fMethod2():string{
return "123";
}
}

我们定义了基类A,并且定义了 私有变量,保护变量,公共变量,私有方法,保护方法,公共方法

定义了继承类B,同样定义了自己的成员,并且构造器重载 和 保护方法重载

父类的实现

 var A = /** @class */
(function() {
function A() {
this.Pro1 = "pro1"; this.fPro1 = "fPro1";
}
A.prototype.fMethod1 = function() {
return "123";
}
;
A.prototype.pMethod1 = function() {
return "456";
}
;
A.prototype.Method1 = function() {}
;
return A;
}()); //闭包作用域

可以看到,通过闭包的的作用域里面新建构造函数给变量A赋值一个构造函数

构造函数里面对类成员变量进行初始化,并且在 原型对象(显示原型)定义类方法。里面js里面没有访问修饰符,我们看到私有方法和公有方法的定义并没有区别。

子类的实现

var B = /** @class */
(function(_super) {
__extends(B, _super);
function B(para) {
var _this = _super.call(this) || this;
_this.Pro2 = "Pro2";
_this.fPro2 = "fPro2";
_this.fPro2 = para;
return _this;
}
B.prototype.Method2 = function() {}
;
B.prototype.pMethod1 = function() {
return _super.prototype.pMethod1.call(this);
}
;
B.prototype.fMethod2 = function() {
return "123";
}
;
return B;
}(A));
exports.B = B;

子类定义的闭包函数传入父类(基类)A做为参数_super,同样也是返回构造函数B.

跟父类不同的是,首先通过调用 __extends函数,对B 和 _super 做了处理。根据js

函数提升的特性,此时B 这个构造函数已经被定义,所以__extends函数的调用应该是在最后面。

在讲解__extends函数之前,我们先看看 重载的定义。

首先是构造器的重载。 构造函数通过调用 A.call(this),父类被调用,父类的构造函数被执行。

这里我们顺便复习一下new 操作符的执行原理:

var obj  = {};//我们创建了一个空对象obj

obj.__proto__ = Base.prototype;//我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象

Base.call(obj);//我们将Base函数对象的this指针替换成obj,然后再调用Base函数,于是我们就给obj对象赋值了一个id成员变量,这个成员变量的值是”base”,关于call函数的用法。

这样我们就知道,当我们new B()的时候,就开始执行B构造函数里面的代码.

在这里,__extends做了什么处理呢,我们来分析一下:

__extends 实现

var __extends = (this && this.__extends) || (function() {
var 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 function(d, b) { //d:父类 b:子类
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype,
new __());
}
;
})();

同样的,函数都是在闭包里面定义的。__extends(B, _super);得知,我们传入父类和子类函数。

首先通过 extendStatics 函数的处理。extendStatics函数这边先不介绍,最后再说。

因为:

new B().__ proto__ === B.prototype

通过上面构造函数的代码里面知道,类成员都是在构造函数里面定义的,其中

方法成员全部定义在prototype里面给所有成员共享。

对于类实例来说,也就是new B() 出来的对象来说,方法成员要全部存放在__ proto__里面的,但是成员方法的来源并不一样,有的是来自于子类,有的来自于

父类,甚至来自与父父类...

如果我们通过原型链来实现的话,很自然的我们希望,每一级的父类对应原型链上的每一级。

  也就是说我们希望:

          new B().__ proto__             存放B定义的所有方法成员 

          new B().__ proto__.__ proto__  存放A定义的所有方法成员
.......

左边是继承链,右边是继承链

我们可能会这么做, B.prototype = new A(), 这个时候new B()的原型对象上的确存在了A和B 的所有成员方法,但是至少有两个问题:

  1. 原型对象上 还放着变量成员,变量成员不应该被共享

  2. 第一级的原型对象上面应该只存放子类B的成员方法

我们看看他们是怎么解决的,既然直接实例A不行,那就是new 一个空的对象。

d.prototype =  new __();

为了保证该空对象有原型功能,我们这么做。

function __() {
this.constructor = d;
}

然后

__.prototype = b.prototype

这样就保证了 new B().__ proto__.__ proto__ 存放A定义的所以方法成员

很完美,我们看看整体代码:

////d:父类 A  b:子类 B
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype,
new __());

静态成员

我们知道,传统面向对象编程语言 静态成员可以通过 static 指定,静态成员跟方法成员一样,可以被共享,typescript也是有static 关键字的, 最终也是定义在 原型对象上面。

extendStatics(d, b); // 通过命名我们也知道,这个是为了解决静态成员的
var 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];
}
;

这个比较简单 ,通过代码我们得知,这个其实就是Object.setPrototypeOf函数及其等价的代码。

Object.setPrototypeOf 定义:

将一个指定的对象的原型设置为另一个对象或者null(既对象的[[Prototype]]内部属性).

B.__ proto__ = A , 为什么需要等价代码呢,是因为 proto 并不是标准(虽然每个浏览器都支持了)。

好了,我们看一下一个子类实例的效果:

typescript  的 polyfill 学习1-Class 继承篇

上一篇:php定时删除文件夹下文件(清理缓存文件)


下一篇:jsp脚本的九个内置对象