天天看點

《JavaScript應用程式設計》一一3.9使用Stamps進行原型繼承

本節書摘來華章計算機出版社《javascript應用程式設計》一書中的第3章,第3.9節,作者:eric elliott 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

對象在javascript中,有各式各樣靈活的特性,相比之下通過object.create()方法所建構出的對象,感覺就像被“閹割”了一樣。開發者總是需要自己去編寫額外的代碼來實作諸如讓享元對象支援資料隐蔽特性這樣的特性,是以當你需要将多個對象上的功能做組合時,一般情況下你會感覺非常無力。

現今多數javascript類庫中提供了一套與類繼承概念相似的對象複用機制,基于原型繼承的javascript類庫占比還是太少,是以就更别提什麼業界标準了。既然少量文法糖就能夠在javascript中模拟類的概念,那麼為什麼不能建構一個囊括了javascript所有強大特性的原型繼承系統呢?

當聊起javascript中的對象建構時,我們不禁首先會問自己,在javascript中,對象所具有的最為重要的特性有哪些?

· 原型代理

· 執行個體狀态

· 封裝性

stamps是一個工廠函數,它擁有一組公有屬性與方法,這些屬性方法用來定義所建構對象的原型代理、預設的執行個體屬性以及為封裝私有屬性所需的函數。在建構對象方面,stamps提供了3種不同的繼承方式。

· 原型代理:原型繼承

· 執行個體狀态:屬性混入

· 封裝性:閉包式繼承

stampit(methods, state, enclose);

讓我們看看如何使用stampit來建構對象:

在上例中,我們在stampit執行個體上調用create()方法,它傳回testobj的執行個體。代碼下方的測試用例顯示,testobj執行個體已經擁有了諸多原型繼承體系下的殺手級特性,你已經無需再花費大量精力去實作自己的工廠函數了。

stampit執行個體支援方法鍊式調用,上例中的對象也可以被這樣建立:

由object.create()建構出的對象會預設使用原型上定義的方法,這些方法被所有執行個體所共享,這樣做在很大程度上節省了記憶體開銷。同理,如果你在程式運作期間修改了原型中的方法,所有執行個體均會受到波及,如下例:

state()方法采用了“屬性混入”的方式,它将傳入的對象視為執行個體的原型代理,并将該對象的每項屬性拷貝後添加至新執行個體中。由于是拷貝而非直接引用,是以你可以放心大膽地對屬性做後期修改。所有stampit執行個體在做對象建構時,均接受一個可選的字典對象,字典對象中的屬性會被混入新執行個體中,這讓對象執行個體化這一過程變得更為簡單。

enclose()方法則采用了“閉包式繼承”的方式, 你可以給它傳入函數,這些函數彼此獨立并且各自擁有閉包作用域,進而可以確定資料的隐蔽性與唯一性。此外,如果在函數中存在多個同名特權方法的定義,那麼後一項始終會有限覆寫前一項。在stampit執行個體中enclose()方法可以被調用多次,所傳入的函數會在對象建構時被“激活”。

在初始化一個對象時,有時并不需要将參數一次性全部傳入,是以我一直建議将對象的執行個體化與初始化解耦,引入讀寫方法(getter/setter)可以解決這個問題。

前面我們介紹了用stampit進行對象建構,其實這僅僅是javascript面向對象特性的冰山一角。接下來我們介紹的内容你很難在現有的javascript流行類庫中尋覓到,甚至連es6規範中都沒有相關定義。

首先,看如何使用閉包來封裝私有資料:

stampit執行個體使用函數作用域來封裝私有資料,請注意讀寫方法(getter/setter)需定義在函數内部,才可以通路到閉包中的變量,這一規則同樣适用于javascript中的所有特權函數。

來看另一個例子:

這個“拼寫錯誤”是有意為之的,它是為了向你展示執行個體a與執行個體b各自封裝的同名私有變量不會對彼此的使用造成影響,而且這麼做的有趣之處在于:

stampit的靜态方法compose()讓你能夠從多個stampit執行個體中做原型繼承。上述示例示範了使用compose()方法來實作多重繼承,我們看到所繼承的屬性中甚至連私有資料都有囊括,而現今的類繼承體系中是做不到這點的。

stampit執行個體有一個名為fixed的特别屬性,它存儲着所有methods(執行個體方法)、state(屬性)與enclose(包裹函數)的對象原型。在執行個體化期間,state對象的所有屬性被拷貝至執行個體中,確定了執行個體屬性操作的安全性;methods對象則作為執行個體的原型代理來使用,進而使得多個執行個體間可以共享一套方法;enclose對象将所有函數當作閉包來使用,實作了對私有資料的通路控制。

compose()方法的用途與$.extend()方法很接近,不過它并不是像$.extend()方法那樣使用外來對象的屬性做擴充,而是借助了stampit執行個體的fixed屬性,compose()方法先是将來自多個stampit執行個體下的fixed屬性做合并,随後向外界傳回一個新stampit執行個體。在對同名屬性進行合并時,compose()會優先使用順序靠後的屬性,在這一點上,它與$.extend(), _.extend()等方法的合并政策是一緻的。

繼續閱讀