Javascript繼承機制的設計思想

做者: 阮一峯javascript

我一直很難理解Javascript語言的繼承機制。html

它沒有"子類"和"父類"的概念,也沒有"類"(class)和"實例"(instance)的區分,全靠一種很奇特的"原型鏈"(prototype chain)模式,來實現繼承。java

我花了不少時間,學習這個部分,還作了不少筆記。可是都屬於強行記憶,沒法從根本上理解。程序員

直到昨天,我讀到法國程序員Vjeux的解釋,才恍然大悟,徹底明白了Javascript爲何這樣設計。編程

下面,我嘗試用本身的語言,來解釋它的設計思想。完全說明白prototype對象究竟是怎麼回事。其實根本就沒那麼複雜,真相很是簡單。瀏覽器

1、從古代提及服務器

要理解Javascript的設計思想,必須從它的誕生提及。網絡

1994年,網景公司(Netscape)發佈了Navigator瀏覽器0.9版。這是歷史上第一個比較成熟的網絡瀏覽器,轟動一時。可是,這個版本的瀏覽器只能用來瀏覽,不具有與訪問者互動的能力。好比,若是網頁上有一欄"用戶名"要求填寫,瀏覽器就沒法判斷訪問者是否真的填寫了,只有讓服務器端判斷。若是沒有填寫,服務器端就返回錯誤,要求用戶從新填寫,這太浪費時間和服務器資源了。編程語言

所以,網景公司急需一種網頁腳本語言,使得瀏覽器能夠與網頁互動。工程師Brendan Eich負責開發這種新語言。他以爲,不必設計得很複雜,這種語言只要可以完成一些簡單操做就夠了,好比判斷用戶有沒有填寫表單。ide

1994年正是面向對象編程(object-oriented programming)最興盛的時期,C++是當時最流行的語言,而Java語言的1.0版即將於第二年推出,Sun公司正在大肆造勢。

Brendan Eich無疑受到了影響,Javascript裏面全部的數據類型都是對象(object),這一點與Java很是類似。可是,他隨即就遇到了一個難題,到底要不要設計"繼承"機制呢?

2、Brendan Eich的選擇

若是真的是一種簡易的腳本語言,其實不須要有"繼承"機制。可是,Javascript裏面都是對象,必須有一種機制,將全部對象聯繫起來。因此,Brendan Eich最後仍是設計了"繼承"。

可是,他不打算引入"類"(class)的概念,由於一旦有了"類",Javascript就是一種完整的面向對象編程語言了,這好像有點太正式了,並且增長了初學者的入門難度。

他考慮到,C++和Java語言都使用new命令,生成實例。

C++的寫法是:

  ClassName *object = new ClassName(param);

Java的寫法是:

  Foo foo = new Foo();

所以,他就把new命令引入了Javascript,用來從原型對象生成一個實例對象。可是,Javascript沒有"類",怎麼來表示原型對象呢?

這時,他想到C++和Java使用new命令時,都會調用"類"的構造函數(constructor)。他就作了一個簡化的設計,在Javascript語言中,new命令後面跟的不是類,而是構造函數。

舉例來講,如今有一個叫作DOG的構造函數,表示狗對象的原型。

  function DOG(name){

    this.name = name;

  }

對這個構造函數使用new,就會生成一個狗對象的實例。

  var dogA = new DOG('大毛');

  alert(dogA.name); // 大毛

注意構造函數中的this關鍵字,它就表明了新建立的實例對象。

3、new運算符的缺點

用構造函數生成實例對象,有一個缺點,那就是沒法共享屬性和方法。

好比,在DOG對象的構造函數中,設置一個實例對象的共有屬性species。

  function DOG(name){

    this.name = name;

    this.species = '犬科';

  }

而後,生成兩個實例對象:

  var dogA = new DOG('大毛');

  var dogB = new DOG('二毛');

這兩個對象的species屬性是獨立的,修改其中一個,不會影響到另外一個。

  dogA.species = '貓科';

  alert(dogB.species); // 顯示"犬科",不受dogA的影響

每個實例對象,都有本身的屬性和方法的副本。這不只沒法作到數據共享,也是極大的資源浪費。

4、prototype屬性的引入

考慮到這一點,Brendan Eich決定爲構造函數設置一個prototype屬性。

這個屬性包含一個對象(如下簡稱"prototype對象"),全部實例對象須要共享的屬性和方法,都放在這個對象裏面;那些不須要共享的屬性和方法,就放在構造函數裏面。

實例對象一旦建立,將自動引用prototype對象的屬性和方法。也就是說,實例對象的屬性和方法,分紅兩種,一種是本地的,另外一種是引用的。

仍是以DOG構造函數爲例,如今用prototype屬性進行改寫:

  function DOG(name){

    this.name = name;

  }

  DOG.prototype = { species : '犬科' };


  var dogA = new DOG('大毛');

  var dogB = new DOG('二毛');


  alert(dogA.species); // 犬科

  alert(dogB.species); // 犬科

如今,species屬性放在prototype對象裏,是兩個實例對象共享的。只要修改了prototype對象,就會同時影響到兩個實例對象。

  DOG.prototype.species = '貓科';


  alert(dogA.species); // 貓科

  alert(dogB.species); // 貓科

5、總結

因爲全部的實例對象共享同一個prototype對象,那麼從外界看起來,prototype對象就好像是實例對象的原型,而實例對象則好像"繼承"了prototype對象同樣。

轉自:http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html

相關文章
相關標籤/搜索