1. 程式人生 > 實用技巧 >JavaScript中的 [[prototype]]、prototype、__proto__ 關係

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)