在extendStatics方法内部虽然代码量相对较多,但是不难发现其实还是主要为了兼容ES5版本的执行环境。在ES6中新增了Object.setPrototypeOf方法用于手动设置对象的原型,但是在ES5的环境中我们一般通过一个非标准的__proto__属性来进行设置,Object.setPrototypeOf方法的原理其实也是通过该属性来设置对象的原型,其实现方式如下:
Object.setPrototypeOf = function(obj, proto) { obj.__proto__ = proto; return obj; }
在extendStatics(d, b)方法中,d指子类Child,b指父类Parent,因此该方法的作用可以解释为:
// 将子类Child的__proto__属性指向父类Parent Child.__proto__ = Parent;
可以将这行代码理解为构造函数的继承,或者叫静态属性和静态方法的继承,即属性和方法不是挂载到构造函数的prototype原型上的,而是直接挂载到构造函数本身,因为在JS中函数本身也可以作为一个对象,并可以为其赋予任何其他的属性,示例如下:
function Foo() { this.x = 1; this.y = 2; } Foo.bar = function() { console.log(3); } Foo.baz = 4; console.log(Foo.bar()) // -> 3 console.log(Foo.baz) // -> 4
因此当我们在子类Child中以Child.someProperty访问属性时,如果子类中不存在就会通过Child.__proto__寻找父类的同名属性,通过这种方式来实现静态属性和静态方法的路径查找。
第二部分:
在第二部分中仅包含以下两行代码:
function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
其中d指子类Child,b指父类Parent,这里对于JS中实现继承的几种方式比较熟悉的同学可以一眼看出,这里使用了寄生组合式继承的方式,通过借用一个中间函数__()来避免当修改子类的prototype上的方法时对父类的prototype所造成的影响。我们知道,在JS中通过构造函数实例化一个对象之后,该对象会拥有一个__proto__属性并指向其构造函数的prototype属性,示例如下:
function Foo() { this.x = 1; this.y = 2; } const foo = new Foo(); foo.__proto__ === Foo.prototype; // -> true
对于本例中,如果通过子类Child来实例化一个对象之后,会产生如下关联:
const child = new Child(); child.__proto__ === (Child.prototype = new __()); child.__proto__.__proto__ === __.prototype === Parent.prototype; // 上述代码等价于下面这种方式 Child.prototype.__proto__ === Parent.prototype;
因此当我们在子类Child的实例child对象中通过child.someMethod()调用某个方法时,如果在实例中不存在该方法,则会沿着__proto__继续往上查找,最终会经过父类Parent的prototype原型,即通过这种方式来实现方法的继承。
基于对以上两个部分的分析,我们可以总结出以下两点:
// 表示构造函数的继承,或者叫做静态属性和静态方法的继承,总是指向父类 1. Child.__proto__ === Parent; // 表示方法的继承,总是指向父类的prototype属性 2. Child.prototype.__proto__ === Parent.prototype;
2、访问修饰符
TypeScript为我们提供了访问修饰符(Access Modifiers)来限制在class外部对内部属性的访问,访问修饰符主要包含以下三种:
public:公共修饰符,其修饰的属性和方法都是公有的,可以在任何地方被访问到,默认情况下所有属性和方法都是public的。
private:私有修饰符,其修饰的属性和方法在class外部不可见。
protected:受保护修饰符,和private比较相似,但是其修饰的属性和方法在子类内部是被允许访问的。
我们通过一些示例来对几种修饰符进行对比:
class Human { public name: string; public age: number; public constructor(name: string, age: number) { this.name = name; this.age = age; } } const man = new Human('tom', 20); console.log(man.name, man.age); // -> tom 20 man.age = 21; console.log(man.age); // -> 21
在上述示例中,由于我们将访问修饰符设置为public,因此我们通过实例man来访问name和age属性是被允许的,同时对age属性重新赋值也是允许的。但是在某些情况下,我们希望某些属性是对外不可见的,同时不允许被修改,那么我们就可以使用private修饰符: