类的prototype属性和__proto__
属性
- 每个对象都有
__proto__
属性,指向对应的构造函数的prototype属性。 - 子类的
__proto__
属性表示构造函数的继承,总是指向父类。 - 子类的prototype属性的
__proto__
属性表示方法的继承,总是指向父类的prototype属性。
1 | class A{} |
这两条继承链,可以这样理解:作为一个对象,子类(B
)的原型(__proto__
属性)是父类(A
);作为一个构造函数,子类(B
)的原型对象(prototype
属性)是父类的原型对象(prototype
属性)的实例。
上面代码的A
,只要是一个有prototype
属性的函数,就能被B
继承。由于函数都有prototype
属性(除了Function.prototype
函数),因此A
可以是任意函数。
以下两种特殊情况
- 子类继承Object类
1 | class A extends Object{} |
- 不存在任何继承
1 | class A{} |
A作为一个基类,即不存在任何继承,就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象即object实例,所以A.prototype.__proto__
指向构造函数Object的prototype属性。
实例的 proto 属性
子类实例的__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性。也就是说,子类的原型的原型,是父类的原型。
1 | class Point { /* ... */ } |
上面代码中,ColorPoint
继承了Point
,导致前者原型的原型是后者的原型。
因此,通过子类实例的__proto__.__proto__
属性,可以修改父类实例的行为。
1 | p2.__proto__.__proto__.printName = function () { |
原生构造函数的继承
原生构造函数是指语言内置的构造函数,通常用来生成数据结构。一般不能继承,有以下九种:
Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()
1 | function MyArray() { |
之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()
或者分配给原型对象都不行。原生构造函数会忽略apply
方法传入的this
,也就是说,原生构造函数的this
无法绑定,导致拿不到内部属性。
ES5 是先新建子类的实例对象this
,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array
构造函数有一个内部属性[[DefineOwnProperty]]
,用来定义新属性时,更新length
属性,这个内部属性无法在子类获取,导致子类的length
属性行为不正常。
ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this
,然后再用子类的构造函数修饰this
,使得父类的所有行为都可以继承。下面是一个继承Array
的例子。
1 | class MyArray extends Array { |