【書名】:你不知道的JavaScript(上卷)
【作者】:Kyle Simpson
【本書總頁碼】:213
【已讀頁碼】:157
顯式混入
由于 JavaScript 不會自動實現(xiàn) Vehicle到 Car 的復制行為孵奶,所以需要手動實現(xiàn)復制功能叠国。這個功能在許多庫和框架中被稱為extend(..)婿着,但是為了方便理解我們稱之為 mixin(..)兰吟。
現(xiàn)在 Car 中就有了一份 Vehicle 屬性和函數(shù)的副本了套鹅。從技術角度來說,函數(shù)實際上沒有被復制诅病,復制的是函數(shù)引用哪亿。所以,Car 中的屬性 ignition 只是從 Vehicle 中復制過來的對于 ignition() 函數(shù)的引用贤笆。相反蝇棉,屬性 engines 就是直接從 Vehicle 中復制了值 1。
Car 已經(jīng)有了 drive 屬性(函數(shù))芥永,所以這個屬性引用并沒有被 mixin 重寫篡殷,從而保留了Car 中定義的同名屬性,實現(xiàn)了“子類”對“父類”屬性的重寫埋涧。
1. 再談多態(tài)
顯式多態(tài):Vehicle.drive.call( this )板辽。
相對多態(tài):inherited:drive()奇瘦。
JavaScript(在 ES6 之前)并沒有相對多態(tài)的機制。所以劲弦,由于 Car 和Vehicle 中都有 drive() 函數(shù)耳标,為了指明調(diào)用對象,必須使用絕對(而不是相對)引用邑跪。通過名稱顯式指定 Vehicle 對象并調(diào)用它的 drive() 函數(shù)次坡。
但是如果直接執(zhí)行 Vehicle.drive(),函數(shù)調(diào)用中的 this 會被綁定到Vehicle 對象而不是Car 對象画畅。因此砸琅,使用 .call(this)來確保 drive() 在 Car 對象的上下文中執(zhí)行。
如果函數(shù) Car.drive() 的名稱標識符并沒有和 Vehicle.drive() 重疊(或者說“屏蔽”)的話轴踱,就不需要實現(xiàn)方法多態(tài)症脂,因為調(diào)用mixin(..) 時會把函數(shù) Vehicle.drive() 的引用復制到 Car 中,因此可以直接訪問this.drive()淫僻。正是由于存在標識符重疊诱篷,所以必須使用更加復雜的顯式偽多態(tài)方法。
在支持相對多態(tài)的面向類的語言中嘁傀,Car 和 Vehicle 之間的聯(lián)系只在類定義的開頭被創(chuàng)建兴蒸,從而只需要在這一個地方維護兩個類的聯(lián)系。
但是在 JavaScript 中(由于屏蔽)使用顯式偽多態(tài)會在所有需要使用(偽)多態(tài)引用的地方創(chuàng)建一個函數(shù)關聯(lián)细办,這會極大地增加維護成本橙凳。此外,由于顯式偽多態(tài)可以模擬多重繼承笑撞,所以它會進一步增加代碼的復雜度和維護難度岛啸。
使用偽多態(tài)通常會導致代碼變得更加復雜、難以閱讀并且難以維護茴肥,因此應當盡量避免使用顯式偽多態(tài)坚踩,因為這樣做往往得不償失。
2. 混合復制
分析一下 mixin(..) 的工作原理瓤狐。它會遍歷 sourceObj的屬性瞬铸,如果在 targetObj沒有這個屬性就會進行復制。由于是在目標對象初始化之后才進行復制础锐,因此一定要小心不要覆蓋目標對象的原有屬性嗓节。
如果是先進行復制然后對 Car 進行特殊化的話,就可以跳過存在性檢查皆警。不過這種方法并不好用并且效率更低拦宣,所以不如第一種方法常用:
這兩種方法都可以把不重疊的內(nèi)容從 Vehicle 中顯性復制到 Car 中。
復制操作完成后,Car 就和 Vehicle 分離了鸵隧,向 Car 中添加屬性不會影響 Vehicle绸罗,反之亦然。
由于兩個對象引用的是同一個函數(shù)豆瘫,因此這種復制(或者說混入)實際上并不能完全模擬面向類的語言中的復制珊蟀。
JavaScript 中的函數(shù)無法(用標準、可靠的方法)真正地復制靡羡,所以只能復制對共享函數(shù)對象的引用系洛。如果修改了共享的函數(shù)對象(比如ignition())俊性,比如添加了一個屬性略步,那 Vehicle 和 Car 都會受到影響。
如果你目標對象中顯式混入超過一個對象定页,就可以部分模仿多重繼承行為趟薄,但是仍沒有直接的方式來處理函數(shù)和屬性的同名問題。
一定要注意典徊,只在能夠提高代碼可讀性的前提下使用顯式混入杭煎,避免使用增加代碼理解難度或者讓對象關系更加復雜的模式。
3. 寄生繼承
顯式混入模式的一種變體被稱為“寄生繼承”卒落,它既是顯式的又是隱式的羡铲,主要推廣者是Douglas Crockford。
下面是它的工作原理:
調(diào)用 new Car() 時會創(chuàng)建一個新對象并綁定到 Car 的 this 上儡毕。但是因為沒有使用這個對象而是返回了我們自己的 car 對象也切,所以最初被創(chuàng)建的這個對象會被丟棄,因此可以不使用 new 關鍵字調(diào)用 Car()腰湾。
這樣做得到的結(jié)果是一樣的雷恃,但是可以避免創(chuàng)建并丟棄多余的對象。
隱式引入
通過在構(gòu)造函數(shù)調(diào)用或者方法調(diào)用中使用 Something.cool.call( this )费坊,實際上“借用”了函數(shù) Something.cool() 并在 Another 的上下文中調(diào)用了它倒槐。最終的結(jié)果是 Something.cool() 中的賦值操作都會應用在 Another 對象上而不是Something 對象上。
因此附井,把 Something 的行為“混入”到了 Another 中讨越。
雖然這類技術利用了 this 的重新綁定功能,但是 Something.cool.call( this ) 仍然無法變成相對(而且更靈活的)引用永毅,所以使用時千萬要小心把跨。通常來說,盡量避免使用這樣的結(jié)構(gòu)卷雕,以保證代碼的整潔和可維護性节猿。