初識原型鏈——怎麽畫一條完整的原型鏈
關於原型鏈的知識,我剛開始就是看一篇一篇的博客,知識有點零碎,後面看了下《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
以及實例對象person1
和person2
的關系如下圖所示:
按照書上的解釋,很好理解,但很顯然,它並不是一條完整的原型鏈,並且我也有疑問:書中給出的示例圖中沒有畫構造函數Person和原型對象Person.prototype的_proto_屬性。
首先:_proto_
屬性是個對象都有,那麽,在萬物皆對象的JS中,構造函數Person
和原型對象Person.prototype
的_proto_
屬性又指向哪裏呢??其次,就這個問題,結合上面給出的代碼,可以將一條完整的原型鏈用類似上面的圖畫出來嗎??
二、再疑惑
之前看了一篇博文,其中分析了_proto_
對應的示例代碼如下:
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 思否 )
從這張大圖中我們可以看出來,
- 所有函數對象的原型(即
fun._proto_
)都是Function.prototype
,無論是JS原生的構造函數如Function還是Object等還是用戶自定義的構造函數如上圖的Foo; - 所有構造函數對應的原型對象的原型(即
fun.prototype._proto_
)都是Object.prototype
,無論是自定義的還是原生的(Object除外); - 存儲在函數對象中的屬性有_proto_、prototype(其實也有constructor,統統指向Function,就連Function自己的constructor也是指向自己,也就是說所有函數對象都是Function的實例,包括它自己);
- 存儲在原型對象中的屬性有_proto_、constructor;
- 存儲在實例對象中的屬性有_proto_(其實也有constructor不過和原型對象中的指向一致)
根據這個圖,可以更好的理解下面這兩句話:
- 由於
_proto_
是任何對象都有的屬性,而JS裏萬物都是對象,所以會形成一條_proto_
連起來的鏈條,遞歸訪問_proto_
必須最終到頭,並且值是null; - 當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
趁著這個機會,可以討論一下原生構造函數Object
和Function
到底什麽關系?
從上圖中我們可以把Function
和Object
部分單獨摘出來:
用代碼將上面的原型鏈表示如下:
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
五、小結
- 所有函數對象的原型(即fun._proto_)都是Function.prototype無論是JS原生的構造函數如Function還是Object等還是用戶自定義的構造函數;
- 所有構造函數對應的原型對象的原型(即fun.prototype._proto_)都是Object.prototype,無論是自定義的還是原生的
- 存儲在函數對象中的屬性有_proto_、prototype(其實也有constructor,統統指向Function,就連Function自己的constructor也是指向自己)
- 存儲在原型對象中的屬性有_proto_、constructor
- 存儲在實例對象中的屬性有_proto_(其實也有constructor不過和原型對象中的指向一致)
Reference:
- JS原型鏈簡單圖解 - 最騷的就是你 - 博客園
- javascript - 為什麽原型鏈的終點是null,而不是Object.prototype - SegmentFault 思否
- 為什麽原型鏈的終點是null,而不是Object.prototype?- 余百煉的博客 - CSDN博客
- 《JS高級程序設計》
初識原型鏈——怎麽畫一條完整的原型鏈