proto

类的prototype属性和__proto__属性

  • 每个对象都有__proto__属性,指向对应的构造函数的prototype属性。
  • 子类的__proto__属性表示构造函数的继承,总是指向父类。
  • 子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{}
class B extends A{}
B.__proto__ === A //true
B.prototype.__proto__ === A.prototype //true

//B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype);
//B继承A的静态属性
Object.setPrototypeOf(B,A);

//Object.setPrototypeOf方法实现
Object.setPrototypeOf = function(obj,proto){
obj.__proto__ = proto;
return obj;
}

这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。

上面代码的A,只要是一个有prototype属性的函数,就能被B继承。由于函数都有prototype属性(除了Function.prototype函数),因此A可以是任意函数。

以下两种特殊情况
  • 子类继承Object类
1
2
3
4
class A extends Object{}
A.__proto__ === Object //true
A.prototype.__proto__ === Object.prototype //true
//A其实就是构造函数Object的复制,A的实例就是Object的实例
  • 不存在任何继承
1
2
3
class A{}
A.__proto__ === Function.prototype //true
A.prototype.__proto__ === Object.prototype //true

A作为一个基类,即不存在任何继承,就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象即object实例,所以A.prototype.__proto__指向构造函数Object的prototype属性。

实例的 proto 属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point { /* ... */ }
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}

toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

上面代码中,ColorPoint继承了Point,导致前者原型的原型是后者的原型。

因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为。

1
2
3
4
5
p2.__proto__.__proto__.printName = function () {
console.log('Ha');
};

p1.printName() // "Ha"
原生构造函数的继承
  • 原生构造函数是指语言内置的构造函数,通常用来生成数据结构。一般不能继承,有以下九种:

    Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function MyArray() {
Array.apply(this, arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
var colors = new MyArray();
colors[0] = "red";
colors.length // 0

colors.length = 0;
colors[0] // "red"

之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。

ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。

ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。下面是一个继承Array的例子。

1
2
3
4
5
6
7
8
9
10
11
12
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined
文章目录
  1. 1. 类的prototype属性和__proto__属性
    1. 1.0.1. 以下两种特殊情况
    2. 1.0.2. 实例的 proto 属性
    3. 1.0.3. 原生构造函数的继承
|