__proto__ 与 prototype

224 阅读3分钟

1. __proto__与prototype

早期的js没有class,用函数代替。

function Puppy() {}

使用new关键字生成实例:

const myPuppy = new Puppy();

函数本身就是构造函数

function Puppy(age) {
  this.puppyAge = age;
}

// 实例化时可以传年龄参数了
const myPuppy = new Puppy(2);

实例化方法用prototype

Java中的类方法,在js用object.prototype为对象添加方法。

Puppy.prototype.say = function() {
    console.log("wangwang!");
}

使用new关键字产生的实例都有类的prototype上的属性和方法。

myPuppy.say();//wangwang!

实例方法查找用__proto__(原型链)

当你访问一个对象上没有的属性时,比如myPuppy.say,对象会去__proto__查找。__proto__的值就等于父类的prototype,myPuppy.__proto__指向了Puppy.prototype

如果你访问的属性在Puppy.prototype也不存在,那又会继续往Puppy.prototype.__proto__上找,这时候其实就找到了Object.prototype了,Object.prototype再往上找就没有了,也就是null,这其实就是原型链

constructor

我们一般说的constructor指的是类的prototype.constructor。它时prototype的一个保留属性,指向类函数本身,用户指示当前类的构造函数。

myPuppy

graph LR
myPuppy-->Puppy.prototype
Puppy.prototype-->Object.prototype
Object.prototype-->null

关系图

2. 继承

就是子类能够找到父类的prototype,最简单的方法就是子类原型的__proto__指向父类原型就行了。

function Parent() {}
function Child() {}

Child.prototype.__proto__ = Parent.prototype;//存在问题!(1)

const obj = new Child();
console.log(obj instanceof Child );   // true
console.log(obj instanceof Parent );   // true

Child没有执行Parent的构造函数

//修改(1)
Child.prototype.__proto__ = new Parent();
//此时会多一个__proto__层级,需要重置constructor
Child.prototype.constructor = Child;

将上面两端代码合并

function Parent() {
  this.parentAge = 50;
}
function Child() {}

Child.prototype = new Parent();
Child.prototype.constructor = Child;      // 注意重置constructor

const obj = new Child();
console.log(obj.parentAge);    // 50

3. 总结

  1. JS中的函数可以作为函数使用,也可以作为类使用
  2. 作为类使用的函数实例化时需要使用new
  3. 为了让函数具有类的功能,函数都具有prototype属性。
  4. 为了让实例化出来的对象能够访问到prototype上的属性和方法,实例对象的__proto__指向了类的prototype。所以prototype是函数的属性,不是对象的。对象拥有的是__proto__,是用来查找prototype的。
  5. prototype.constructor指向的是构造函数,也就是类函数本身。改变这个指针并不能改变构造函数。
  6. 对象本身并没有constructor属性,你访问到的是原型链上的prototype.constructor
  7. 函数本身也是对象,也具有__proto__,他指向的是JS内置对象Function的原型Function.prototype。所以你才能调用func.call,func.apply这些方法,你调用的其实是Function.prototype.callFunction.prototype.apply
  8. prototype本身也是对象,所以他也有__proto__,指向了他父级的prototype。__proto__prototype的这种链式指向构成了JS的原型链。原型链的最终指向是Object的原型。Object上面原型链是null,即Object.prototype.__proto__ === null
  9. Function.__proto__ === Function.prototype。这是因为JS中所有函数的原型都是Function.prototype,也就是说所有函数都是Function的实例。Function本身也是可以作为函数使用的----Function(),所以他也是Function的一个实例。类似的还有ObjectArray等,他们也可以作为函数使用:Object(), Array()。所以他们本身的原型也是Function.prototype,即Object.__proto__ === Function.prototype。换句话说,这些可以new的内置对象其实都是一个类,就像我们的Puppy类一样。
  10. ES6的class其实是函数类的一种语法糖,书写起来更清晰,但原理是一样的。