在js中,prototype和__proto__是很难理解的两个知识点,一是它俩名字差不多,容易混淆;二是在工作中能直接用到这两个知识点的地方可能也不是特别多,但是这两个知识点却是非常重要的,尤其在实现继承方面。
一、理解这俩名字
在我学习它俩的时候,最大的障碍就是名字,第一印象就感觉它俩是一个东西。如果以这种印象作为前提的话,就很容易走入一个迷宫。所以怎么理解prototype和__proto__呢?
我个人理解的是:prototype指的是扩展; __proto__指的链子(原型链),所以我觉得如果用__chain__来替代__proto__能更好的理解这个链子,或者把proto两边的下划线想象作链条,看到链条就想起原型链。
二、链子__proto__链到了哪
先来个例子:
function Dog(age){
this.age = age;
}
Dog.prototype.name = "狗";
var dog = new Dog(3);
复制代码
此时dog是Dog的一个实例,我们可以打印下dog看看:
发现dog私有属性只有age,打印下dog.name也能得到"狗"。这是为什么呢?dog除了私有属性age外,还有个__proto__,点开dog的__proto__看一下:
发现dog的__proto__中有了name这个属性,那打印dog.name是不是就返回的这个name呢?
实际就是返回的这个name,js中访问一个对象的属性,会遵循一个规则:先在这个对象本身找私有属性,如果找到了就返回,如果找不到就沿着原型链往上找,直到最顶层null还找不到就会返回undefined。
那dog.__proto__为什么会有name这个属性呢?这是因为在构建dog实例的时候,会把dog的__proto__指向它的构造函数的prototype,简单说就是实例的链子会链到构造函数的扩展上,验证一下:
console.log(dog.__proto__ === Dog.prototype) //true
复制代码
一个实例的链子默认会往上链接到它的构造函数的扩展,并且能访问这个扩展上面的属性,除非手动改变这个链子链接的扩展:
function Dog(age){
this.age = age;
}
Dog.prototype.name = "狗";
function Cat(){
this.type = "cat";
}
Cat.prototype.say = "miao~";
var dog = new Dog(3);
//手动改变了dog的链子
dog.__proto__ = Cat.prototype;
console.log(dog.name); //undefined
console.log(dog.say); //miao~
console.log(dog.age); //3
console.log(dog.type); //undefined
//手动改变__proto__指向Cat.prototype 还会出现下面的情况
console.log(dog instanceof Dog); //false
console.log(dog instanceof Cat); //true
复制代码
虽然把dog的链子链到了Cat的扩展上了,但是打印了dog.type还是undefined,这说明属性只能在链子所链的扩展上找,跟是谁的扩展没有很大的关联。这种情况很类似使用Object.create()来生成的实例:
function Cat(){
this.type = "cat";
}
Cat.prototype.say = "miao~";
var dog = Object.create(Cat.prototype, {
age:{
value:10
}
})
console.log(dog.age); //10
console.log(dog.say); //miao~
conosle.log(dog.type); //undefined
//生成的实例的链子指向了 Object.create传入的第一个参数
console.log(dog.__proto__ === Cat.prototype); //true
复制代码
三、谁具有默认的prototype
js只有函数默认拥有prototype属性,由构造函数构造出来的实例默认是不具有扩展的,除非手动给这个实例加上扩展:
function Dog(age){
this.age = age;
}
Dog.prototype.name = "狗";
var dog = new Dog(3);
console.log(dog.prototype) //undefined
//给dog添加一个叫prototype的属性
dog.prototype = {
say:"wangwang~";
}
复制代码
手动给实例添加prototype,并没有什么实际意义,就只是给一个对象添加一个属性,如上给dog添加了prototype,跟以下类似:
dog = {
age:3,
prototype:{
say:"wangwang~"
},
prototype2:{
say:"wangwangwang~"
}
}
复制代码
四、prototype里面都有啥
以一个普通的函数为例,函数的prototype首先会有constructor属性,constructor指向了这个函数本身:
function test(a) {
console.log(a)
}
console.log(test.prototype.constructor === test); //true
复制代码
还会有__proto__属性,为了方便我们把函数的prototype称为fnPrototype,fnPrototypey也是个实例对象,它也会有构造函数,因此它的链子也会链到了构造它的函数的扩展。
其实fnPrototypey是Object的一个实例,它的链子链到Object的扩展上,我们可以验证下:
function test(a) {
console.log(a)
}
var fnPrototype = test.prototype;
console.log(fnPrototype.__proto__ === Object.prototype); //true
复制代码
我们平时用到prototype比较多的情况会有:给Array、String、Function或者Object等js的源生构造器重写或者添加扩展方法和属性:
Array.prototype.replaceAll = function(){
//code...
}
String.prototype.trim = function(){
//code...
}
Function.prototype.bindFn = function(){
//code...
}
Object.prototype.__type = "object";
复制代码
这些js源生的构造器也会有prototype属性,每个构造器会有自己的一些对应方法,他们构造出来的实例的链子会链到这些扩展上,因此实例就能使用这些方法和属性。 Function.prototype是个比较特殊的情况,它实际是一个源生封装的函数,但是实现了__proto__链接到了Object.prototype,Object.prototype.__proto__指向了null,所以说js中的一切皆对象(虽然Object也是一个函数,这里就不纠结到底是函数层级高还是对象层级高了,就认为Object层级是最高的吧):
console.log(Function.prototype.__proto__ === Object.prototype); //true
复制代码
五、总结
简单总结一下,实例的链子会链接到构造函数的扩展。访问实例的属性,先在本身找,找不到就沿着链子往上找链的扩展,再在这个扩展上找,还找不到就沿着这个扩展的链子再往上找。