說到prototype,就不得不先說下new的過程。
我們先看看這樣一段代碼:
<script type="text/javascript">
var Person = function () { };
var p = new Person();
</script>
很簡單的一段代碼,我們來看看這個new究竟做了什么?我們可以把new的過程拆分成以下三步:
var p={}; 也就是說,初始化一個對象p。
p.__proto__
=Person.prototype;Person.call(p);也就是說構造p,也可以稱之為初始化p。
關鍵在于第二步,我們來證明一下:
<script type="text/javascript">
var Person = function () { };
var p = new Person();
alert(p.__proto__ === Person.prototype);
</script>
這段代碼會返回true。說明我們步驟2的正確。
那么__proto__
是什么?我們在這里簡單地說下。每個對象都會在其內部初始化一個屬性,就是__proto__
,當我們訪問一個對象的屬性 時,如果這個對象內部不存在這個屬性,那么他就會去__proto__
里找這個屬性,這個__proto__
又會有自己的__proto__
,于是就這樣 一直找下去,也就是我們平時所說的原型鏈的概念。
按照標準,__proto__
是不對外公開的,也就是說是個私有屬性,但是Firefox的引擎將他暴露了出來成為了一個共有的屬性,我們可以對外訪問和設置。
好,概念說清了,讓我們看一下下面這些代碼:
<script type="text/javascript">
var Person = function () { };
Person.prototype.Say = function () {
alert("Person say");
}
var p = new Person();
p.Say();
</script>
這段代碼很簡單,相信每個人都這樣寫過,那就讓我們看下為什么p可以訪問Person的Say。
首先var p=new Person();可以得出p.__proto__
=Person.prototype。那么當我們調用p.Say()時,首先p中沒有Say這個屬性, 于是,他就需要到他的__proto__
中去找,也就是Person.prototype,而我們在上面定義了 Person.prototype.Say=function(){}; 于是,就找到了這個方法。
好,接下來,讓我們看個更復雜的。
<script type="text/javascript">
var Person = function () { };
Person.prototype.Say = function () {
alert("Person say");
}
Person.prototype.Salary = 50000;
var Programmer = function () { };
Programmer.prototype = new Person();
Programmer.prototype.WriteCode = function () {
alert("programmer writes code");
};
Programmer.prototype.Salary = 500;
var p = new Programmer();
p.Say();
p.WriteCode();
alert(p.Salary);
</script>
我們來做這樣的推導:
var p=new Programmer()可以得出p.__proto__
=Programmer.prototype;
而在上面我們指定了Programmer.prototype=new Person();我們來這樣拆分,var p1=new Person();Programmer.prototype=p1;那么:
p1.__proto__
=Person.prototype;
Programmer.prototype.__proto__
=Person.prototype;
由根據上面得到p.__proto__
=Programmer.prototype。可以得到p.__proto__.__proto__
=Person.prototype。
好,算清楚了之后我們來看上面的結果,p.Say()。由于p沒有Say這個屬性,于是去p.__proto__
,也就是 Programmer.prototype,也就是p1中去找,由于p1中也沒有Say,那就去p.__proto__.__proto__
,也就是 Person.prototype中去找,于是就找到了alert(“Person say”)的方法。
其余的也都是同樣的道理。
這也就是原型鏈的實現原理。
最后,其實prototype只是一個假象,他在實現原型鏈中只是起到了一個輔助作用,換句話說,他只是在new的時候有著一定的價值,而原型鏈的本質,其實在于__proto__
!
如果還不明白,可以看以下解析:
__proto__
、prototype傻傻分不清楚? 記住以下兩點:
1.__proto__
是每個對象都有的一個屬性,而prototype是函數才會有的屬性。
2.__proto__
指向的是當前對象的原型對象,而prototype指向的,是以當前函數作為構造函數構造出來的對象的原型對象。看起來有點繞,我 show you the code:
//在JavaScript的世界中,所有的函數都能作為構造函數,構造出一個對象
//下面我給自己構造一個女神做對象
function NvShen () {
this.name = "Alice";
}
//現在我設置NvShen這個函數的prototype屬性
//一般來說直接用匿名的對象就行,我這里是為了方便理解,
//先定義一個hand對象再把hand賦值給NvShen的prototype
var hand = {
whichOne: "right hand",
someFunction: function(){
console.log("not safe for work.");
}
};
NvShen.prototype = hand;
//這個時候,我們可以用NvShen作為構造函數,構造出myObject對象
var myObject = new NvShen();
console.log(myObject.__proto__ === NvShen.prototype) //true
好了,通過上面的代碼,我們構建了一個女神對象myObject,而myObject的原型是hand對象,而剛好myObject的構造函數NvShen()的prototype屬性也指向hand對象。現在我們知道,prototype與__proto__
的關系就是:你的__proto__
來自你構造函數的prototype
還有,上面的例子中,myObject是通過new NvShen()創建的,而hand對象,則是賦值語句創建的,這有什么不同?
其實hand這種直接用賦值語句加花括號來創建的對象,叫做對象字面量,你可以想象JavaScript內置了一個叫Object()的構造函數,這個函數的prototype屬性指向的是一個空對象:
console.log(Object.prototype) //輸出{}
而所有對象字面量都是通過Object()構造出來的,換言之,對象字面量的__proto__
屬性都指向Object.prototype, which is 一個空對象。
所以我們可以知道, hand.__proto__
指向的是Object.prototype 再附送你一個fun fact:
Object.prototype這個對象,它的__proto__
指向的是null,然后就沒有然后了。
最后如果你還是不太明白,可以看各位知乎大神的回答:js中proto和prototype的區別和關系?