一张图理解JS中的原型和原型链
前言
面试必问的原型和原型链问题,先来开具一张图,后面就全靠想了
1.构造函数
从上面的图片开始分析,我们先定义一个构造函数,并且创建一个实例
1 | function Foo(){} |
上述代码发生了几件事
1.定义了一个函数,定义函数时会根据特定的规则为该函数创建一个prototype属性,这个属性指向一个对象,即函数的原型。这个对象也会默认带有一个constructor属性,指向函数本身;
2.通过new操作符调用这个函数,并返回赋值给f1,f1就是构造函数Foo的实例。f1是一个对象,在js中,所有的对象在创建时都会被添加一个[[prototype]]属性,目前大部门浏览器可以通过proto属性访问[[prototype]]属性(此处可以认为是同一个东西),es5中可以使用Object.getPrototypeOf可以直接访问这个属性;
3.f1对象的[[prototype]]指向了创建f1的构造函数Foo的原型属性Foo.prototype,这个属性连接的是f1和构造函数原型,而不是连接的构造函数本身,可以通过以下方式验证
1 | f1.__proto__ === Foo.prototype //true |
我们常说的实例可以调用构造函数原型上的方法,就是因为[[prototype]]属性的存在,当实例访问自身身上不存在的属性or方法时,会尝试通过[[prototype]]属性去访问创建它的构造函数Foo的原型上即Foo.prototype是否存在对应的属性,我们可以看到图上最上面的线 f1.proto –> Foo.prototype
2.原型链
我们同样可以看到,如果当f1通过proto访问Foo.prototype后,发现Foo.prototype还是没有找到对应的属性或方法,那么此时f1还会继续查找。
别忘了Foo.prototype也是一个对象,既然是对象那么就是Object的实例,那么Foo.prototype也就自然的有一个[[prototype]]属性指向Object的原型了,可以用一下方式验证。
1 | Foo.prototype.__proto__ === Object.prototype // true |
所以,当f1通过proto访问Foo.prototype后发现找不到想要的属性或方法,那么会继续根据Foo.prototype.proto访问Object.prototype,如果找到了会访问该属性或调用该方法,且会停止查找。如果还是没有找到,根据图上来看,最后会查找到null。
f1 -> proto -> Foo.prototype -> proto -> Object.prototype -> proto -> null
这样看着像一条线的逐级查找,且是通过函数的原型来查找,这就是所谓的原型链。
为了验证原型链真的存在,我们还可以举个例子
1 | f1.toString() // [object Object] |
很明显,我们并没有在f1上定义一个toString方法,包括在创建它的构造函数的原型中也没有,而在Object.prototype中有一个toString方法,且二者是相等的,所以f1调用toString方法也就是通过原型链去查找最后在Object.prototype找到了对应的方法。
2.函数也有原型链
我们继续看图,在javascript中,函数也是对象的一种,自然函数也是有原型链的,而函数实际上都是Function的实例,自然酥油函数都会有一个[[prototype]]指向Function.prototype了,另外不管是普通函数还是内置的函数比如Function、Object、Array等,既然它们是函数,也会有一个[[prototype]]指向Function.prototype。
1 | Foo.toString() // "function Foo() {}" |
此时调用Foo.toString方法,会发现与Object.prototype.toString方法调用的结果不一样,这是因为Foo通过原型链,在Foo.prototype上就找到了对应的方法,然后停止查找了。
另外,在这幅图中,有一个循环的指向
Object -> proto -> Function.prototype -> proto -> Object.prototype -> constructor -> Object
这也很好理解,Object是函数,所以会有原型链查找到Function.prototype再查找到Object自己的原型Object.prototype,而Object.prototype是对象,且在创建的时候会自动添加上一个constructor的属性指向Object函数自身,所以会有这么一个看似是“鸡生蛋,蛋生鸡”的结果。
4.原型链相关
如何判断两个对象是否通过原型链有关联呢,我们可以通过instanceof操作符和Object.prototype.isPrototypeOf来判断
1 | f1 instanceof Foo // true |
当Foo.prototype自身没有isPrototypeOf时,通过原型链查找到了Object.prototype上找到了对应的方法。
instanceof和isPrototypeOf的区别:
instanceof是用原型链上查找的各个函数的原型,再通过该原型对应自身的函数判断,即instanceof右侧的值为函数且该函数的原型出现在左侧的值的原型链上
isPrototypeOf则是直接通过原型对象查找,不经过原型的函数本身了
二者比较而言还是isPrototypeOf更通用更直观一些。