從零構建JavaScript的對象系統


 一、正統的類與繼承

       類是對象的定義,而對象是類的實例(Instance)。類不可直接使用,要想使用就必須在內存上生成該類的副本,這個副本就是對象。

       以Java為例:

       public class Group { } // 創建一個類

       Group a = new Group(); // 實例化一個對象

       通過繼承,子類可以直接從父類獲得其所有的屬性和方法,繼承的實現機制是"復制、拷貝"。

       public class Child extends Parent { } // 創建一個子類,繼承父類的方法

 

二、原型與繼承 

       和正統的面向對象語言不同,JavaScript中不存在正統意義的"類"。原因是Brendan Eich在設計JavaScript的時候,不希望又重新設計一門面向對象的語言(因為已經有C++和Java了),並且他希望把JavaScript設計得更簡單,因此他借鑒了Java的語法,用構造函數代替類,所以JavaScript中的對象是通過構造函數創建的。

       這並不奇怪,JavaScript本身就是一個借鑒多種語言,倉促交媾的產物。

       嚴格的講,JavaScript中既不存在類,也不存在實例,盡管這些概念是如此的深入人心,以致於誤用起來是那么順其自然,但我們還是需要明白這一點。

       沒有類,那么JavaScript怎么實現繼承呢? Brendan Eich為構造函數設置了一個prototype屬性。這個屬性包含一個對象(即"原型對象"),所有實例對象需要共享的屬性和方法,都放在這個對象里面;不需要共享的屬性和方法,就放在構造函數里面。

       與類繼承的"復制、拷貝"不同,原型繼承的機制是"引用、關聯"。JavaScript中所有的對象都是由構造函數生成的,每個對象都共同繼承構造函數的原型對象中的屬性和方法。

       在對象內部有兩種方式訪問它繼承的原型:

       1、obj.constructor.prototype

       2、obj.__proto__

       第一種方法,源於每個對象都擁有一個內置屬性constructor指向其構造函數;第二種方法源於每個對象都有一個內部指針[[prototype]],直接指向其原型,這個內部指針是不可訪問的,但是有些瀏覽器暴露出一個__proto__屬性,用於直接訪問原型對象。

 

三、原型鏈與繼承

       因為所有對象都是由構造函數生成的,也就是說所有對象,都必然繼承某個原型,而原型本身也是對象,它也會繼承別的原型,如此環環相扣就形成了原型鏈。

       JavaScript的對象系統是一個類似族譜的樹狀結構,每一個分支都通過原型鏈一脈相承。

   基於原型鏈的繼承機制和作用域鏈的工作機制非常類似:當調用對象的某個屬性時,如果對象的自有屬性中不存在該屬性,那么JavaScript引擎就會沿着該對象的原型鏈向上查找,直到找到第一個匹配的屬性名為止。

       原型鏈總有個盡頭吧,就好像DOM只有唯一的根元素一樣,所有原型鏈都匯聚到同一個源頭,那就是Object.prototype,它也是一個對象,也有[[prototype]]屬性,那么Object.prototype.__proto__ === ?答案是null。

       有人特別擅長發揮,就根據這個大談“JavaScript原型設計的哲學思想”,什么道生於無,一生二,二生三,三生萬物……這個真的想多了,一個花兩周時間搞出來的“KPI項目”,怎么也扯不到哲學上去,這就是外行學前端的一個寫照。原型鏈系統中每個對象都必須關聯到另一個對象,並且不能循環引用,那么原型鏈就只能無限延伸下去,這顯然是不可能的,因此只好把個不倫不類的 null 拿來作為原型鏈的終結點,這也在一定程度上解釋了 null 明明不是對象,但是它的數據類型卻是對象。

 

四、原型的關聯規則

       每個對象的[[prototype]]具體指向誰,或者說每個對象的原型究竟是誰,取決於對象的創建方式。

1、對象直接量的原型對象是Object.prototype;

       var obj = { }; obj.__proto__ === Object.prototype // true

2、通過Object.create( )創建的對象,其原型是由第一個參數指定的對象;

  對象直接量等價於Object.create(Object.prototype)

3、通過構造函數創建的對象,其原型即構造函數的原型對象。

   內置的構造函數的prototype原型是預設好的,我們可以修改。內置構造函數的prototype原型並不都是普通的對象,例如:

       typeof Function.prototype // "function"

       Array.isArray( Array.prototype ) // true 

       但這不重要。

   自定義的構造函數,它的原型默認是一個僅含constructor屬性的對象。

   function f () {}; Object.getOwnPropertyNames(f.prototype) // "constructor"

       比較特殊的是函數也有繼承的原型。注意,這里很容易混淆“函數的繼承原型”與“函數的prototype原型”,它們是兩回事,函數不會從自身的prototype指向的原型對象中繼承任何屬性或方法,所有的函數都共同繼承一個原型對象,那就是Function.prototype。

       Array.__proto__ === Function.prototype // true

       Function.__proto__ === Function.prototype // true

   Object.__proto__ === Function.prototype // true

       var f = function () { }; f.__proto__ === Function.prototype // true

       為什么函數也有__proto__屬性?這不奇怪,因為函數也是對象(可調用的對象)。

 

五、從零構建JavaScript對象系統      

       根據以上規則,我們試着從零開始構建JavaScript的對象系統。

       首先,創造一個構造函數Object,為JavaScript空空如也的對象世界播下第一顆種子。然后給它添一些屬性和方法。在Chrome控制台輸入Object.getOwnPropertyNames(Object),可以看到Object有二十五個屬性,其中就有prototype。

       prototype幾乎就是JavaScript的繁育后代的生殖系統,所以它很關鍵,我們需要傳給它一些公共屬性和方法:

   Object.prototype = {/* properties */}

    然后需要給Object定義私有屬性,雖然它是函數,但畢竟函數也是對象,所以能夠定義屬性:

       Object.create = function ( ) { }; Object.keys = function ( ) { } ……

   同樣的我們需要給Function設置原型,但是這個原型不是普通對象,而是函數,因此不能用對象直接量: 

       Function.prototype = {/* properties */} // wrong

       Function.prototype = function () { } // right

       至於為什么這么做,規范里只有規定,沒有解釋。

       (參考:http://stackoverflow.com/questions/39698919/why-typeoffunction-prototype-is-function)

       然后將Function.prototype.__proto__ 設置為 Object.prototype。

       如法炮制,於是我們有了九大構造函數:Object、Function、Array、String、Number、Boolean、Date、Error、RegExp。

       再來兩個單體內置對象:Math和JSON

       var Math = new Object(); // 這里采用new“實例化”了一個對象

       Math.random = ……

       Math.max = ……

       ……

 

       現在原型對象之間的關聯有了,函數是不是也效仿着弄一個繼承呢,這樣就免得給每個函數都定義相同的方法了,簡單粗暴,直接將函數的[[prototype]]都指向Function.prototype完事兒。

         

    JavaScript內置的原型系統基本上就完成了,其它的構造函數和對象就留給程序員自定義設置吧。 

 

參考:阮一峰《JavaScript繼承機制的設計思想》

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM