TypeScript中的原型继承

3,398 阅读4分钟

Point3D继承Point

【注:以下TypeScript简称为TS】

我们从一段实现类继承的代码开始(出自TS):

var Point3D = (function (_super) {
    __extends(Point3D, _super);
    function Point3D(x, y, z) {
        _super.call(this, x, y);
        this.z = z;
    } P
    Point3D.prototype.add = function (point) {
        var point2D = _super.prototype.add.call(this, point);
        return new Point3D(point2D.x, point2D.y, this.z + point.z);
    };
    return Point3D;
})(Point);

这种被叫做立即调用函数表达式(IIFE [Immediately-Invoked Function Expression])

  • 在这里_super也就是基类Point。

__extends实现如下(这段代码也是TS里实现类继承的简化版本):

var __extends = this.__extends || function (Child, Parent) {
    for (var p in Parent) if (Parent.hasOwnProperty(p)) Child[p] = Parent[p];
    function __() { this.constructor = Child; }
    __.prototype = Parent.prototype;
    Child.prototype = new __();
};
  • Child是子类,Parent是父类
  • Child,Parent都只是函数,并不是实例,new出来的才是实例。
  • 把父类静态属性传给子类静态属性。for (var p in Parent) if (Parent.hasOwnProperty(p)) Child[p] = Parent[p];这里的静态属性是指直接用hasOwnProperty能够访问到的属性,而并非原型链上的属性。
  • 接下去的3行代码,主要目的是把子类的函数原型与父类的函数原型对接上,用一句代码表示的话就是Child.prototype.__proto__ = Parent.prototype,然后为啥一句话就能搞定,但是TS要写3句话,那是因为__proto__这个属性有兼容性问题,有些浏览器叫法不同,或者有些根本不让你去访问到,但确确实实在其内部存在这么一个东西,接下去我们先来把Child.prototype.__proto__ = Parent.prototype这个解释通,然后再来说下剩下的3行代码

Child.prototype.proto = Parent.prototype

首先先来名词解释一下

1 __proto__

在JS里,所有实例对象都有一个__proto__(抛开浏览器兼容性,只说概念),作用是:如果访问某个对象的属性obj.abc没有的话,那就会去obj.__proto__.abc去找,如果还是没有找到,那就去obj.__proto__.__proto__.abc找,以此类推,一直到__proto__为null结束,如果还没找到,那就真的没有了。这个就是js支持的原型继承。举例:

var foo = {}
// setup on foo as well as foo.__proto__
foo.bar = 123;
foo.__proto__.bar = 456;
console.log(foo.bar); // 123
delete foo.bar; // remove from object
console.log(foo.bar); // 456
delete foo.__proto__.bar; // remove from foo.__proto__
console.log(foo.bar); // undefined

2 prototype

__proto__是给实例用的,而prototype是给函数用的,每个JS里的function都有个prototype(这个貌似没有兼容性问题,比较统一),然后prototype有一个成员叫constructor,这个constructor又反过来指向函数自己。代码如下:

function Foo() { }
console.log(Foo.prototype); // {} i.e. it exists and is not undefined
console.log(Foo.prototype.constructor === Foo); // Has a member called `constructor` pointing

3 用了new调用构造函数之后,函数里的this做了些啥

this在函数里指向了当前这个被新创建出来的对象实例,一般我们会在构造函数里用this来改一下这个实例的初始值等。

function Foo() {
    this.bar = 123;
} 

// call with the new operator
var newFoo = new Foo();
console.log(newFoo.bar); // 123

我觉得可以这么认为,用了new关键字来调用函数后,不但创建了一个新对象,而且在函数最后强制return了里面的this,从而能够赋值给左边那个变量(newFoo)

4 用了new调用构造函数之后,prototype__proto__做了些啥

调用了new之后,会把这个函数的prototype赋值给这个新对象实例的__proto__属性。举例:

function Foo() { }
var foo = new Foo();
console.log(foo.__proto__ === Foo.prototype); // True!

再来看看__extends剩下的3行

1 function __() { this.constructor = Child; }
2 __.prototype = Parent.prototype;
3 Child.prototype = new __();

我们倒过来看,

第3行 Child.prototype = new __() 意思其实是: Child.prototype = {__proto__ : __.prototype} (上面第4点)

然后结合第2行代码 __.prototype = Parent.prototype , 其实就变成了 Child.prototype = {__proto__ : Parent.prototype}

到这里,其实我们已经完成目标Child.prototype.__proto__ = Parent.prototype

然后为了和普通函数拥有相同的定义,参考第1点,我们需要再复原一下Child.prototype.constructor,这也就是第一行function __() {this.constructor = Child; }做的事情,最后就变成了Child.prototype = {__proto__ : Parent.prototype , constructor : Child} (上面第2点)

然后疑问来了,我们为啥要复原这个constructor,其实没有这个constructor,我们大部分继承功能都能使用,具体可以参考一下这里

大概理解为,这个玩意只有在你自己手动调用的时候才有用,比如有一个var ball = new Football();,你用着用着,发现忘了这个ball是Football还是Basketball构造出来的,你确实又想要一个和ball同类型的,那你可以这样来 var ball2 = ball.constructor();,这样ball2和ball就一样了,不过好像用不太到就是。。。如果有小伙伴知道更多的理解,麻烦评论给我哈。。。。

这篇文章是看了TypeScript Deep Dive关于TS类转成JS实现那部分之后总结的,如有不对的地方还希望大家评论给我。