JavaScript中的 [[prototype]]、prototype、__proto__ 關係
說到 prototype、__proto__ 首先就得去理解 JavaScript 語言的繼承機制。在典型的面向物件的語言中,如C#,都存在類(class)的概念, 類就是物件的模板,物件就是類的例項。C# 中的 繼承允許我們根據一個類來定義另一個類,這使得建立和維護應用程式變得更容易。同時也有利於重用程式碼和節省開發時間。在 JavaScript 是不存在 類 和 子類 概念的,全靠原型鏈的模式來實現繼承 (後面出class在這基礎上的語法糖)。
這裡舉一個簡單C# 類和繼承例子,一類 People,其中包含一個Say方法。另外兩個類 Man 和 Woman 都繼承 People這個類,這樣這兩個子類都擁有了父類的Say方法。這是一個典型的繼承
//C# Code using System; namespace ConsoleApp { class Program { static void Main(string[] args) { Man man = new Man(); man.Work(); man.Say("累死爺了"); Woman woman = new Woman(); woman.Shop(); woman.Say("花錢真爽"); Console.Read(); } } class People { public People() { } public void Say(string text) { Console.WriteLine(text); } } class Man : People { public void Work() { Console.WriteLine("工作工作.....賺錢賺錢..."); } } class Woman : People { public void Shop() { Console.WriteLine("買買買....."); } } }
prototype 的由來
當初JavaScript之父在開發JavaScript的時候,覺得沒有必要做的很複雜,只需要做些簡單操作就可以了,所以沒有引入類的概念。
在JavaScript中也是通過 new 生成一個物件例項的,例如在C#中生成一個物件例項會這樣寫 new People(),會呼叫類的建構函式。在JavaScript簡化了這個操作,new 後面跟的不是類,而是建構函式。
用JavaScript來實現上面C#例子
var people = { Say:function(text){ console.log(text) } } function Man(){ this.Work=function(){ console.log("工作工作.....賺錢賺錢..."); } this.Say=people.Say; } function Woman(){ this.Shop=function(){ console.log("買買買....."); } this.Say=people.Say; } var man = new Man(); man.Work(); man.Say("累死爺了"); var woman = new Woman(); woman.Shop(); woman.Say("花錢真爽");
但是用建構函式生成例項物件,有一個缺點,那就是無法共享屬性和方法。
people.Say=function(){
console.log("...")
}
man.Say("累死爺了"); //累死爺了
woman.Say("花錢真爽");//花錢真爽
var man2=new Man();
man2.Say();//...
man.Say=null;
woman.Say("花錢真爽");//花錢真爽
//這裡即使修改了 people.Say 也不會對以生成例項有影響,修改一個也不會對另一個有影響,每個例項都是相互獨立的
每一個例項物件,都有自己的屬性和方法的副本。這不僅無法做到資料共享,也是極大的資源浪費。於是JavaScript之父 為了解決這個問題就在建構函式加入了個prototype屬性。這個屬性是一個物件(常稱 原型物件),把一些公用的屬性和方法都放在這個物件裡,一些是私有的就放在建構函式裡。
例項物件被建立時候,會自動引用原型物件的屬性和方法,這樣例項物件屬性就分為兩種了,一種是建構函式裡私有自己的,另一種則是來著引用 原型物件。一旦原型物件屬性和方法修改後,例項的物件也會跟著變化。原型物件 不僅共享了資料,也減少了對資源的佔用。
那麼用Js來實現上面C#例子就可以這樣寫
var people = {
Say:function(text){
console.log(text)
}
}
function Man(){
this.Work=function(){
console.log("工作工作.....賺錢賺錢...");
}
}
Man.prototype = people;
function Woman(){
this.Shop=function(){
console.log("買買買.....");
}
}
Woman.prototype = people;
var man = new Man();
man.Work();
man.Say("累死爺了");
var woman = new Woman();
woman.Shop();
woman.Say("花錢真爽");
//這個時候加入個 people 在加入個 eat 方法, man 和 woman 例項物件也是可以使用的
people.Eat=function(){
console.log("吃吃吃.....");
}
man.Eat();
woman.Eat();
prototype 和 __proto__ 和 [[prototype]] 關係
JavaScript中任意物件都有一個內建屬性[[prototype]],在ES5之前沒有標準的方法訪問這個內建屬性,但是大多數瀏覽器都支援通過 __proto__ 來訪問,
prototype 和 __proto__ 關係
JavaScript 只有一種結構:物件。每個例項物件( object )都有一個私有屬性(稱之為 __proto__ )指向它的建構函式的原型物件( prototype )。該原型物件也有一個自己的原型物件( __proto__ ) ,層層向上直到一個物件的原型物件為 null。根據定義,null 沒有原型,並作為這個原型鏈中的最後一個環節。幾乎所有 JavaScript 中的物件都是位於原型鏈頂端的 Object 的例項。
看文字可能會有點蒙,用上面例子來舉例說明下
var people = {
Say:function(text){
console.log(text)
}
}
function Man(){}
Man.prototype = people;
var man = new Man();
/* man 是建構函式的原型物件是 Man.prototyp
所以 man.__proto__ === Man.prototype */
console.log(man.__proto__ === Man.prototype && man.__proto__ === people) //true
/* Man 建構函式也是物件,也有自己的 __proto__ 屬性
而函式 Man 是由 Function 構造的 */
console.log(Man.__proto__ === Function.prototype)
/* Function 物件也有自己的 __proto__ 屬性
Function 是由 Function自己例項來的 */
console.log(Function.__proto__ === Function.prototype)
/* Object 和 Function 同理*/
console.log(Object.__proto__ === Function.prototype)
console.log(Object.__proto__ === Function.__proto__)
/* Function 的 原型物件 也有自己的 __proto__
Function 的 原型物件 是由 Object 來的*/
console.log(Function.prototype.__proto__ === Object.prototype)
/* Object 的 原型物件 在最頂層 所以他的 __proto__ 為 null */
console.log(Object.prototype.__proto__ === null)