ES2015 中的函数式Mixin
生活随笔
收集整理的這篇文章主要介紹了
ES2015 中的函数式Mixin
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
原文鏈接:http://raganwald.com/2015/06/17/functional-mixins.html
在“原型即對象”中,我們看到可以對原型使用 Object.assign 來模擬 mixin,原型是 JavaScript 中類概念的基石。現在我們將回顧這個概念,并進一步探究如何將功能糅合進類。
首先,簡單回顧一下:在 JavaScript 中,類是通過一個構造函數和它的原型來定義的,無論你是用 ES5 語法,還是使用 class 關鍵字。類的實例是通過 new 調用構造器的方式創建的。實例從構造器的 prototype 屬性上繼承共享的方法。
對象 mixin 模式
如果多個類共享某些行為,或者希望對龐雜的原型對象進行功能提取,這時候就可以使用 mixin 來對原型進行擴展。? ? 如這里的 Todo 類 class Todo {constructor (name) {this.name = name || 'Untitled';this.done = false;}do () {this.done = true;return this;}undo () {this.done = false;return this;} } 用于顏色編碼的 mixin 如下: const Coloured = {setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;} }; 將顏色編碼功能糅合到 Todo 原型上是簡而易行的: Object.assign(Todo.prototype, Coloured);new Todo('test').setColourRGB({r: 1, g: 2, b: 3})//=> {"name":"test","done":false,"colourCode":{"r":1,"g":2,"b":3}} 我們還可以升級為使用私有屬性: const colourCode = Symbol("colourCode");const Coloured = {setColourRGB ({r, g, b}) {this[colourCode]= {r, g, b};return this;},getColourRGB () {return this[colourCode];} }; 至此,非常簡單明了。我們將這稱為一種 “模式”,像菜譜一樣,是解決某種問題的獨特的代碼組織方式。 ?函數式?mixin
上面的對象 mixin 功能完好,但用它來解決問題要分兩步走:定義 mixin 然后擴展類的原型。Angus Croll 指出將 mixin 定義成函數而不是對象會是更優雅的做法,并稱之為函數式 mixin。再次以 Coloured 為例,將它改寫成函數的形式: const Coloured = (target) =>Object.assign(target, {setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;}});Coloured(Todo.prototype); 我們可以定義一個工廠函數,并從命名上體現該模式: const FunctionalMixin = (behaviour) =>target => Object.assign(target, behaviour); 現在我們可以精要地定義函數式 mixin: const Coloured = FunctionalMixin({setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;} });可枚舉性?
如果我們探究 class 聲明類的方式下對 prototype 的操作,可以發現聲明的方法默認是不可枚舉的。這可以避免一個常見問題——遍歷實例的 key 時程序員有時忘記檢測 .hasOwnProperty。 ? 而我們的對象 mixin 模式無法做到這點,定義在 mixin 中的方法默認是可枚舉的。如果我們故意將其設置為不可枚舉,Object.assign 就不會將它們糅合到目標原型了,因為 Object.assign 只會將可枚舉的屬性賦值到目標對象上。 ? 這將導致以下情形: Coloured(Todo.prototype)const urgent = new Todo("finish blog post"); urgent.setColourRGB({r: 256, g: 0, b: 0});for (let property in urgent) console.log(property);// => namedonecolourCodesetColourRGBgetColourRGB 正如所見,setColourRGB 和 getColourRGB 方法被枚舉出來了,而 do 和 undo 方法卻沒有。這對于不健壯的代碼會是個問題,因為我們不可能每次都重寫別處的代碼,處處加上 hasOwnProperty 檢測。 ? 該問題使用函數式 mixin 便可迎刃而解,我們可以神乎其神地讓 mixin 表現得和 class 聲明類似,這是函數式 mixin 的好處之一: const FunctionalMixin = (behaviour) =>function (target) {for (let property of Reflect.ownKeys(behaviour))Object.defineProperty(target, property, { value: behaviour[property] })return target;} 將上面 mixin 的主體部分作為一種代碼模板一遍遍寫出來不但累人而且容易出錯,而將其封裝到函數里則是一種小進步。 ? ?mixin 的職責
和類一樣,mixin 是元對象:它們給實例定義行為。除了以方法的形式定義對象的行為,類還負責初始化實例。有的時候,類和元對象還會具有其他的功能。 ? 例如,有時某個概念涉及到一組人盡皆知的常量。如果使用類,那么將這些常量定義在 class 本身上則非常方便,這時 class 本身充當了命名空間的作用。 class Todo {constructor (name) {this.name = name || Todo.DEFAULT_NAME;this.done = false;}do () {this.done = true;return this;}undo () {this.done = false;return this;} }Todo.DEFAULT_NAME = 'Untitled';// If we are sticklers for read-only constants, we could write: // Object.defineProperty(Todo, 'DEFAULT_NAME', {value: 'Untitled'}); 我們無法使用 “簡單 mixin” 做同樣的事,因為默認情況下,“簡單 mixin” 的所有屬性最終都被糅合到實例的 prototype 上。例如,我們想定義 Coloured.RED, Coloured.GREEN, Coloured.BLUE,但我們并不想在任何實例個體上定義 RED, GREEN, BLUE。 ? 同樣,我們可以借助函數式 mixin 來解決該問題。FunctionalMixin 工廠函數將接收一個可選的字典,該字典包含只讀的 mixin 屬性,該字典通過一個特殊的鍵給出: const shared = Symbol("shared");function FunctionalMixin (behaviour) {const instanceKeys = Reflect.ownKeys(behaviour).filter(key => key !== shared);const sharedBehaviour = behaviour[shared] || {};const sharedKeys = Reflect.ownKeys(sharedBehaviour);function mixin (target) {for (let property of instanceKeys)Object.defineProperty(target, property, { value: behaviour[property] });return target;}for (let property of sharedKeys)Object.defineProperty(mixin, property, {value: sharedBehaviour[property],enumerable: sharedBehaviour.propertyIsEnumerable(property)});return mixin; }FunctionalMixin.shared = shared; 現在我們便可以這樣寫: const Coloured = FunctionalMixin({setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;},[FunctionalMixin.shared]: {RED: { r: 255, g: 0, b: 0 },GREEN: { r: 0, g: 255, b: 0 },BLUE: { r: 0, g: 0, b: 255 },} });Coloured(Todo.prototype)const urgent = new Todo("finish blog post"); urgent.setColourRGB(Coloured.RED);urgent.getColourRGB()//=> {"r":255,"g":0,"b":0}?
mixin 本身的方法
JavaScript 中屬性未必是值(和函數相對)。有時候,類也具有方法。同樣,有時 mixin 具有自己的方法也是合理的,比如當涉及到 instanceof 時。 ? 在 ECMAScript 的之前版本中,instanceof 操作符檢查實例的 prototype 是否和構造函數的 prototype 相匹配。它和“類”配合使用沒啥問題,但卻無法直接和 mixin 協同工作。 urgent instanceof Todo//=> true urgent instanceof Coloured//=> false 這是 mixin 存在的問題。另外,程序員可能根據需要創建動態類型,或者直接使用 Object.create 和 Object.setPrototypeOf 管理原型,它們都可能導致 instanceof 工作不正常。ECMAScript 2015 提供了一種方式來覆寫內置的 instanceof 的行為,即對象可以定義一個特殊的方法,該方法屬性的名字是一個既定的符號——Symbol.hasInstance。 ? 我們可以簡單測試一下: Object.defineProperty(Coloured, Symbol.hasInstance, {value: (instance) => true}); urgent instanceof Coloured//=> true {} instanceof Coloured//=> true 當然,上面的例子在語義上是不對的。然而借助該技術,我們可以這樣做: const shared = Symbol("shared");function FunctionalMixin (behaviour) {const instanceKeys = Reflect.ownKeys(behaviour).filter(key => key !== shared);const sharedBehaviour = behaviour[shared] || {};const sharedKeys = Reflect.ownKeys(sharedBehaviour);const typeTag = Symbol("isA");function mixin (target) {for (let property of instanceKeys)Object.defineProperty(target, property, { value: behaviour[property] });target[typeTag] = true;return target;}for (let property of sharedKeys)Object.defineProperty(mixin, property, {value: sharedBehaviour[property],enumerable: sharedBehaviour.propertyIsEnumerable(property)});Object.defineProperty(mixin, Symbol.hasInstance, {value: (instance) => !!instance[typeTag]});return mixin; }FunctionalMixin.shared = shared;urgent instanceof Coloured//=> true {} instanceof Coloured//=> false 你需要為了照顧 instanceof 而專門做此實現嗎?很可能不需要,因為自己實現一套多態機制是不得已而為之的做法。但這一點使得編寫測試用例很順手,并且一些激進的框架開發者可能在函數的多分派和模式匹配上求索,或許會用上這一點。 ?總結
對象 mixin 的迷人之處在于簡單:它不需要在對象字面值和 Object.assign 之上做一層抽象。 ? 然而,通過 mixin 模式定義的行為和通過 class 關鍵字定義的行為存在些許差異。體現差異的兩個例子分別是可枚舉性以及 mixin 自身的屬性(如常量和類似于 [Symbol.hasInstance] 的 mixin 自身方法)。 ? 函數式 mixin 使得實現上述功能成為可能,不過生成函數式 mixin 的 FunctionalMixin 函數引入了一定復雜性。 ? 一般來說,最好保持同一個問題域下的代碼表現盡可能相似,而這有時不可避免地增加基礎代碼的復雜性。但這一點更多的是一種指導思想,而非需要恪守的萬能教條。出于此,對象 mixin 模式和函數式 mixin 在 JavaScript 中都有各自的一席之地。轉載于:https://www.cnblogs.com/averyby/p/10026365.html
總結
以上是生活随笔為你收集整理的ES2015 中的函数式Mixin的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何使用crash工具分析Linux内核
- 下一篇: 人力资源资质备案是什么意思?