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