JS继承&原型链

146 阅读3分钟

基于__proto__和prototype的原型链

关于__proto__属性,MDN上的解释如下:

The proto property of Object.prototype is an accessor property (a getter function and a setter function) that exposes the internal [[Prototype]] (either an object or null) of the object through which it is accessed.

即是说,__proto__ 属性指向了实例对象的原型Constructor.prototype。

首先用一张图来总结__proto__prototype的关系:

实例的__proto__

function Foo() {}
var f1 = new Foo();
f1.__proto__ == Foo.prototype; //true

通过new方法创建函数Foo的实例f1,f1.__proto__会指向Foo.prototype,进而继承Foo函数原型上的所有属性和方法。 在JS中,只有函数有prototype属性,基于prototype可以去模拟实现类和继承。

函数原型和构造器

f1.constructor === Foo.prototype.constructor; //true
Foo.prototype.constructor === Foo; //true
f1.constructor === Foo; //true

f1是Foo的示例,它的constructor就是Foo函数原型对象中的constructor,而Foo函数原型上的constructor就指向函数本身。

对象的__proto__

var one = {x: 1};
var two = new Object();
one.__proto__ === Object.prototype // true
two.__proto__ === Object.prototype // true
one.toString === one.__proto__.toString // true

不管是隐式还是显式创建的对象,对象的__proto__都是Object.prototype,Object实际上是一个js函数function Object(){},所以拥有prototype属性。two = new Object()中Object是构造函数,所以two.__proto__就是Object.prototype。至于one,ES规范定义对象字面量的原型就是Object.prototype

one.constructor === Object.prototype.constructor;
Object.prototype.constructor === Object;
one.constructor === Object;

函数的__proto__

Foo.constructor === Function; //true
Foo.__proto__ === Function.prototype; // true

函数Foo的构造函数是function Function(){},所以函数Foo的__proto__都指向Function.prototype

Function.__proto__ === Function.prototype; // true
Array.__proto__ === Function.prototype; // true
Object.__proto__ === Function.prototype; // true
String.__proto__ === Function.prototype; // true

Function本身就是函数,所以Function.__proto__就是Function.prototype,二者为同一对象。同理,Object/Array/String等等构造函数本质上和Function一样,均继承于Function.prototype

Function.prototype的__proto__

Function.prototype直接继承自Object.prototypeFunction.prototype.__proto__就是Object.prototype,二者为同一对象。

    Function.prototype.__proto__ === Object.prototype; //true
    Function.prototype instanceof Object; //true
    Function.prototype instanceof Function; //false

通过这点我们可以弄清继承的原型链:Object.prototype--->Function.prototype--->Function|Object|Array...。。 综上所述可以得出:

    Function.__proto__.__proto__ === Object.prototype;

所以Function是Object的实例

    Function instanceof Object; //true

此外Object作为函数,继承了Function.prototype的方法,所以Object又是Function的实例。

    Object.__proto__ === Function.prototype;
    Object instanceof Function; //true

哈哈,二者互为实例,这就是有名的鸡生蛋和蛋生鸡的关系。

Object.prototype的__proto__

原型链的尽头(root)是Object.prototype。所有对象均从Object.prototype继承属性。

Object.prototype.__proto__ === null; //true

REFS:

es6继承

class通过extends实现继承,比起es5基于原型链、借用构造函数实现继承方便很多。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();

上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined。 这是由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

  • set的时候,super为this子类实例
  • get的时候,super获取的是A.prototype

大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。

function test(){}
test.__proto__ = test.constructor.prototype

es6中extends实现的继承,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

B.__proto__ === A
B.prototype.__proto__ === A.prototype
//对应es5实现
B.prototype = new A()

实现继承的关键:

  • 子类B.prototype的__proto__属性指向A.prototype,即B.prototype.__proto__ = A.prototype,表示将继承父类原型上的属性和方法;
  • 子类B的__proto__属性总是指向父类A,即B.__proto__ = A,表示将继承父类的静态属性和方法。

这样的结果是因为,类的继承是按照下面的模式实现的。

class A {}

class B {}

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

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

const b = new B();

Object.setPrototypeOf方法的实现,会将proto对象作为obj的原型。

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

本质上,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。