初识原型链——怎么画一条完整的原型链

关于原型链的知识,我刚开始就是看一篇一篇的博客,知识有点零碎,后面看了下《JS高级程序设计》,又大致整合了一下。下面记录的是,对于一条完整的原型链长什么样这个问题的思考。

一、疑惑

借用一段在《JS高级程序设计》中的一段代码:

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

此段代码中,构造函数Person,原型对象Person.prototype以及实例对象person1person2的关系如下图所示:

按照书上的解释,很好理解,但很显然,它并不是一条完整的原型链,并且我也有疑问:书中给出的示例图中没有画构造函数Person和原型对象Person.prototype的_proto_属性。

首先:_proto_属性是个对象都有,那么,在万物皆对象的JS中,构造函数Person和原型对象Person.prototype_proto_属性又指向哪里呢??其次,就这个问题,结合上面给出的代码,可以将一条完整的原型链用类似上面的图画出来吗??

二、再疑惑

之前看了一篇博文,其中分析了_proto_应该指向谁的问题,举了下面这个例子,可能直接看会有点蒙圈,原文: JS原型链简单图解 - 最骚的就是你 - 博客园

对应的示例代码如下:

var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(A.prototype即A的原型对象)
console.log(a.__proto__.__proto__); //Object {}(Object.prototype即Object的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null

其实,对于a._proto_ ==A.prototype以及Object.prototype._proto_==null比较好理解:

使用函数表达式方法创建的函数变量A,就是新的函数变量a的构造函数,所以a的原型对象就是其构造函数的prototype所指的对象:A.prototype
而Object.prototype._proto_为什么是null可以参考:为什么原型链的终点是null,而不是Object.prototype?- 余百炼的博客 - CSDN博客

然而,为什么A.prototype的原型是Object.prototype,即A.prototype._proto_==Object.prototype怎么解释呢??

三、解答

其实,原型链是指对象的原型链,这个链上的节点都是一级一级的原型对象,所以原型链上的所有节点都是对象!!!因此这就好理解了,A.prototype 是一个对象,然后它是Object的一个实例,所以它的原型是Object.prototype

这里需要注意的是,a虽然是原型链上的起点,它也是对象,但是它的原型并不是直接就是Object.prototype,它的原型是其构造函数的prototype所指的对象:A.prototype

其实这个图描述的还只是局部 ,不利于理解,请看下面的图:(图片取自javascript - 为什么原型链的终点是null,而不是Object.prototype - SegmentFault 思否



从这张大图中我们可以看出来,

  1. 所有函数对象的原型(即fun._proto_)都是Function.prototype,无论是JS原生的构造函数如Function还是Object等还是用户自定义的构造函数如上图的Foo;
  2. 所有构造函数对应的原型对象的原型(即fun.prototype._proto_)都是Object.prototype,无论是自定义的还是原生的(Object除外);
  3. 存储在函数对象中的属性有_proto_、prototype(其实也有constructor,统统指向Function,就连Function自己的constructor也是指向自己,也就是说所有函数对象都是Function的实例,包括它自己);
  4. 存储在原型对象中的属性有_proto_、constructor;
  5. 存储在实例对象中的属性有_proto_(其实也有constructor不过和原型对象中的指向一致)

根据这个图,可以更好的理解下面这两句话:

  1. 由于_proto_是任何对象都有的属性,而JS里万物都是对象,所以会形成一条_proto_连起来的链条,递归访问_proto_必须最终到头,并且值是null;
  2. 当JS引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会在原型链上查找,但不会查找自身的prototype

四、再解答

这样的话,关系就比较明了了。所以,文章开头的那段代码的原型链应该如下图所示:




由于绘图工具问题,function Function应该还有一个指向自己的constructor没画出来。这个图对应的代码又贴在了下面,离得近方便看…………

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

趁着这个机会,可以讨论一下原生构造函数ObjectFunction到底什么关系?
从上图中我们可以把FunctionObject部分单独摘出来:




用代码将上面的原型链表示如下:

Function._proto_==Function.prototype==function(){}
Function._proto_._proto_==Object.prototype
Function._proto_._proto_._proto_==Object.prototype._proto_==null

Object._proto_ === Function.prototype==function(){}
Object._proto_._proto_==Object.prototype
Object._proto_._proto_._proto_==null

从上图中可以看到构造函数之间的关系如下:

Object.prototype.constructor===Object
Object instanceof Function;//true
Object.constructor===Function

Function.prototype.constructor===Function
Function instanceof Object;//true
Function.constructor===Function

五、小结

  1. 所有函数对象的原型(即fun._proto_)都是Function.prototype无论是JS原生的构造函数如Function还是Object等还是用户自定义的构造函数;
  2. 所有构造函数对应的原型对象的原型(即fun.prototype._proto_)都是Object.prototype,无论是自定义的还是原生的
  3. 存储在函数对象中的属性有_proto_、prototype(其实也有constructor,统统指向Function,就连Function自己的constructor也是指向自己)
  4. 存储在原型对象中的属性有_proto_、constructor
  5. 存储在实例对象中的属性有_proto_(其实也有constructor不过和原型对象中的指向一致)

Reference:

  1. JS原型链简单图解 - 最骚的就是你 - 博客园
  2. javascript - 为什么原型链的终点是null,而不是Object.prototype - SegmentFault 思否
  3. 为什么原型链的终点是null,而不是Object.prototype?- 余百炼的博客 - CSDN博客
  4. 《JS高级程序设计》

猜你喜欢

转载自www.cnblogs.com/ch206265/p/10629689.html