首先,我们暂且把object类型和function类型分开来,因为 function是一个特殊的对象类型,我们这里这是便于区分,把function类型单独拿出来。顺便一提,typeof也是单独把function设为一种类型,我们可以用typeof来验证我们的猜想。
从上图中可以看到,所有的橘色箭头(constructor)都指向function Function(){},所有的黑色箭头(__proto__)开始有两条路可以走,但是最后都汇聚在了一起,指向null。而天蓝色箭头(prototype)不具有连续性,一般都是一个function指向一个object。然后我们可以得出以下结论:
所有对象的构造函数(constructor)最终指向function Function( ){ }这个函数,包括他自己。ps:为了方便,我们称它为母Function。
所有对象的__proto__最终指向null,即一切原型链的末端就是null。
所谓的一切源于对象,这里的”对象”指的是Object{}(红色虚线框内,也就是Object的prototype)。ps:为了方便,我们在这里叫它母Object。
除了Object的原型(prototype)直接指向母Object,其他所有类型的(构造器的)原型(prototype)都先指向一个由母Object派生出来的一个子Object,再指向母Object,Function的原型为function{}。ps:构造器,即构造函数。
-
哲学部分:这幅图包含两个”先有鸡还是先有蛋”的问题:
function(){}是由母Function构造的,但它同时又是母Function的原型。。
function(){}的隐式原型(__proto__)是母Object,母Object是由构造函数 function Object(){}构造的,但函数function Object(){}的隐式原型又是function (){}。。。。
当然除了”先有鸡还是先有蛋”,原型链中还有一个最最有哲理的问题:构造函数function Function(){}的构造函数就是他自己,就是说,他自己把自己生下来了。。。。。 = =
至于为什么function(){}有俩个框框,其实是因为它是原型中最特殊的。它是原型中唯一一个的function类型的,其他的原型都是object类型。可能是考虑到它也是由构造函数Function生成的吧,所以typeof返回的值也是function。
我们再来说一下可能大家都很疑惑的问题,就是原型(prototype)和隐式原型(__proto__)的区别。
我说一下我的见解。相对于”原型”,我觉得”__proto__”更适合叫做父对象,因为在原型链中,负责连接各个对象的,就是”__proto__”。也就是说,我改写对象的”prototype”,并不会影响对象在原型链中的位置,想脱离或者改变原型链,只能是改写”__proto__”。
在原型链中,对象和它的”__proto__”指向的对象的关系,更像是别的语言中的子类和父类。子类会继承父类的方法和属性,而且会和父类保持一样的类型。
一般来说,”__proto__”都是指向某一个对象的”prototype”属性,所以对象会继承的也就是其父对象”prototype”中的属性和方法,但是并不会继承其父对象自身的属性方法。说的可能有点绕,我们举个栗子:
//以Object为原型创建一个对象,obj。var obj=new Object/*这句话可以翻译为:var obj={}obj.__proto__=Object.prototype*/
这个时候,obj的”__proto__”指向的是Object.prototype,所以obj的父对象是Object.prototype,obj会继承Object.prototype中的属性方法,但是并不会继承Object中的属性和方法。这时候我们用obj.toString()是可以的,但是用obj.length就会报错。这是因为toString()是写在Object.prototype里面的,而length是写在Object里面的。
在学习运算符的时候,相比很多初学者也会有和我一样的疑问,为什么instanceof向上查找会在prototype里查找,而不在”父对象”中查找。理解我上面所说,大家相比也会明白了吧。就拿上面的例子来说,其实Object只是obj的构造器,构造函数而已,obj真正的父对象是Object.prototype。那讲到这里有的小伙伴就会有疑问了,那我要是想继承Object里面的属性怎么办?其实也很简单,设置obj的”__proto__”指向Object就可以了。不过有一点,”__proto__”属性毕竟是底杠开头,是官方不想暴露在外面的属性,能不用的时候最好不要用。其实就算不用”__proto__”也是可以达到想要的效果的。Object里面有一个可以创建对象的方法create,用它我们可以轻松达到我们的要求。举个栗子://以Object为原型创建一个对象,obj。var obj= Object.create(Object)/*这句话可以翻译为:var obj={}obj.__proto__=Object再说一下create的用法:Object.create(prototype,[{code}]);//返回一个对象可以看到,这里有两个参数,第一个参数prototype相当于创建对象的__proto__,值得话随便一个对象就可以了,第二个参数可以不填,里面详细写创建对象的一些属性和他们的属性标签。注意,create创建的是一个对象,和new一样,无论以什么为父对象,返回值都是object。*/
再来说说prototype,通过上图大家会发现,prototype属性只是一个函数和一个对象之间的一个桥梁,在这里为什么要说是桥梁呢,为什么不说是函数的一个属性呢?我曾写过一个例子:
function fa(){}fa.prototype.fname=’fa’var ch=new fa()ch.fname //’fa’fa.prototype={fname:’newfa’} //改写prototypech.fname //依旧是’fa’,ch.hasOwnProporty(‘fname’) //falsech.__proto__==fa.prototype //false
通过上述代码大家可以看出来了吧,ch继承fa时只是让ch.__proto__指向了fa.prototype指向的地址,而不是指向fa.prototype。所以就导致了改写fa.prototype并不会影响ch.fname的值。改写后ch.__proto__不等于fa.prototype了,也就是说ch和fa已经半毛钱关系都没有了。
基本上到这里,大家都会对原型链有一定的认识了,至于对象中的特殊存在——function,大家可以自己去探索一下。以上都是自己对于JS中原型链的认识,如有错误欢迎大家指正~ps:sf的编排好难用 !