乐趣区

TypeScript中Class继承的JS实现

新建 index.ts 文件,实现 SubType 继承 SuperType。

//index.ts
class SuperType {
    name: String;
    constructor(name:String){this.name = name;}
    getName():String{return this.name}
}
class SubType extends SuperType {constructor(name:String){super(name);
    }
}

将 index.ts 编译为 index.js

//index.js
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 SuperType = (function () {function SuperType(name) {this.name = name;}
    SuperType.prototype.getName = function () {return this.name;};
    return SuperType;
}());
var SubType = (function (_super) {__extends(SubType, _super);
    function SubType(name) {return _super.call(this, name) || this;
    }
    return SubType;
}(SuperType));

我们首先分析编译后的 SuperType, 可以看出 SuperType 就是一个自执行函数,执行过程中生成构造函数并将原型方法绑定在构造函数上,最终返回构造函数。

/* 创建自执行函数 */
var SuperType = (function () {
    /* 构造函数 */
    function SuperType(name) {this.name = name;}
    /* 绑定原型方法 */
    SuperType.prototype.getName = function () {return this.name;};
    /* 返回构造函数 */
    return SuperType;
}());

然后 SubType 与 SuperType 有两点不同,多了两个地方。

/* 自执行函数,参数_super 值为 SuperType*/
var SubType = (function (_super) {
    /* 继承函数 */
    __extends(SubType, _super);
    function SubType(name) {
        /* 将父类的实例属性和方法绑定到子类的实例 */
        return _super.call(this, name) || this;
    }
    return SubType;
}(SuperType));

先看 _super.call(this, name);,这里实际是执行 SuperType 构造函数并将构造函数的 this 指向当前创建的 SubType 实例。
要明白这一点首先要知道在 new 一个实例的时候发生了什么。

/* 当我们 new 一个实例时 */
var o = new SubType();
/* 我们可以理解为 */
/*
1. 创建一个空的对象
2. 将对象的__proto__指向类的 prototype
3. 调用构造函数并将构造函数的 this 指向对象, 以此将构造函数上的属性绑定在对象上。*/
var o = {};
o.__proto__ = SubType.prototype;
SubType().call(o,name)

因为在执行 SubType().call(o,name) 时 子类构造函数 SubType() 中的 this 已经指向实例 o,所以当调用 子类中的 _super.call(this, name) 其 this 即指向实例 o,因此便可将 父类构造函数 SuperType()中的实例属性和方法继承到了在子类的实例上。

接下来来看 SubType() 下的 __extends() 方法。

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 __());
    };
})();

首先判断 __extends() 方法是否已经被声明。

/* 若在当前环境下已经被声明,则返回 this.__extends,否则返回(function(){})();  */
var __extends = (this && this.__extends) || (function () {...})();

然后看返回的自执行函数 (function(){})()

var __extends = (function () {var extendStatics = function (d, b){...}
    // 返回一个匿名函数
    return function (d, b) {// 调用闭包中 extendStatics()方法
        extendStatics(d, b);
        // 定义一个构造函数__()
        function __() { this.constructor = d;}
        // 判断 b 是否为空,是的话则 d.prototype = Object.create(b)
        // 否则 d.prototype = (__.prototype = b.prototype, new __())
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();

重点看d.prototype = (__.prototype = b.prototype, new __());

d.prototype = (__.prototype = b.prototype, new __())
// 其步骤为
//1. 将__.prototype 指向 b 即父类的 prototype
  __.prototype = b.prototype;
//2. 实例化一个__()
  let _o = new __();
//3. 将 d 即子类的的 prototype 指向 new __()
  d.prototype = _o;

// 相当于做了
d.prototype.__proto__ = b.prototype;
d.prototype.constructor = d;

以此实现了子类对父类原型方法的继承。

下面来看闭包中 extendStatics = function (d, b){...} 的定义

var extendStatics = function (d, b) {
    // 当浏览器支持 Object.setPrototypeOf 时返回
    extendStatics = Object.setPrototypeOf ||
        // 当浏览器支持__proto__时返回
        ({__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);

这一段代码做了兼容性处理,实际在做的就是 d.__proto__ = b 即实现了 子类对父类静态方法的继承。

总结

typescript 的继承,实际上相对用 javascript 实现了

function SubType(){SuperType().call(this);
}
SubType.prototype.constructor = SubType;
SubType.prototype.__proto__ = SuperType.prototype;
SubType.__proto__ = SuperType;

了解了 typescript 的继承原理后,我们在使用 typescript 继承时就要注意一些问题,比如深浅拷贝。

PS

在看代码时碰到的小点,在此记录。
{__proto__: [] } instanceof Array是如何工作的?
个人理解为当浏览器不支持 __proto__ 时,{__proto__: [] }相当于为对象 {} 定义了一个值为 []__proto__属性;但当浏览器支持 __proto__ 时,相当于将 {} 的原型指向了一个 Array 实例。

退出移动版