mixin配合class實現多繼承的絕佳妙用

Github 源碼地址

什麼是mixin

mixin一般翻譯爲“混入”、“混合”,
早期一般解釋爲:把一個對象的方法和屬性拷貝到另一個對象上;
也可以簡單理解爲能夠被繼承的類,
最終目的是實現代碼的複用。

從一個需求說起

爲了使你能夠最快的清楚我在說什麼,我們從一個需求說起:

一個項目中有多個彈層需求;
一些是公共方法,比如點擊關閉按鈕關閉彈層;
一些彈層是可以拖動的,且有蒙層;
一些彈層是可以縮放的;
其他都是業務方法,無可複用性。

你可以先在心裏想下,如果是你,你會怎樣完成這個需求?

腦海中規劃下

我們爲公共方法寫個類:BaseModal
爲可拖動的彈層寫個類:DragModal
爲可縮放的彈層寫個類:ScaleModal
爲自定義的業務需求寫個類:CustomModal

畫個腦圖的話,會是下面圖片中的樣子:

不同類之間的關係圖

extends簡單實現下

看代碼

// 公共方法
class BaseModal {
  close(){
    console.log('close');
  }
}

// 可以拖動的彈層,我們寫一個單獨的類
class DragModal extends BaseModal {
  hasLayer = true;
  drag() {
    console.log('drag');
  }
}

// 可以縮放的彈層,我們寫一個單獨的類
class ScaleModal extends BaseModal {
  scale() {
    console.log('scale');
  }
}

// 業務方法
class CustomModal extends DragModal {
  close(){
    console.log('custom-close');
  }
  do() {
    console.log('do');
  }
}

let c = new CustomModal();
c.close(); // custom-close
c.drag(); // drag
c.do(); // do
c.hasLayer; // true

拋出問題

  • 如何使CustomModal能夠同時繼承DragModalScaleModal
  • 某個相同方法希望不覆蓋,而是都執行

試試早期的mixin方法實現多繼承

看代碼

// 可以拖動的彈層,我們寫一個單獨的類
class DragModal extends BaseModal {
  hasLayer = true;
  drag() {
    console.log('drag');
  }
}

// 可以縮放的彈層,我們寫一個單獨的類
class ScaleModal extends BaseModal {
  scale() {
    console.log('scale');
  }
}

// 獲取原型對象的所有屬性和方法
function getPrototypes(ClassPrototype) {
  return Object.getOwnPropertyNames(ClassPrototype).slice(1);
}

function mix(...mixins){
  return function(target){
    if (!mixins || !Array.isArray(mixins)) return target;
    let cp = target.prototype;
    for (let C of mixins) {
      let mp = C.prototype;
      for (let m of getPrototypes(mp)) {
        cp[m] = mp[m];
      }
    }
  }
}
@mix(DragModal, ScaleModal)
class CustomModal {
  scale(){
    console.log('custom-scale');
  } 
  do() {
    console.log('do');
  }
}
let c = new CustomModal();
c.close(); // 報錯,因爲dobase沒在A或B的prototype上,而是在A.prototype.__proto__上
c.drag(); // drag
c.scale(); // scale  並非是我們想要的custom-scale
console.log(c.hasLayer); // undefined

存在的問題

以上mix方式實現了多繼承,但存在以下問題

  • 會修改target類的原型對象
  • target類的相同方法名會被被繼承類的相同方法名覆蓋
  • 實例屬性無法繼承
  • BaseModal類無法被繼承

只繼承不修改prototype的實現方式

看代碼

class BaseModal {
  close() {
    console.log('close');
  }
}

let DragModalMixin = (extendsClass) => class extends extendsClass {
  hasLayer = true;
  drag() {
    console.log('drag');
  }
};

class CustomModal extends DragModalMixin(BaseModal) {
  drag() {
    console.log('custom-drag');
  }
  do() {
    console.log('do');
  }
}

let c = new CustomModal();

c.close(); // close
c.drag(); // custom-drag
console.log(c.hasLayer); // true

存在的問題

如何讓CustomModal再繼承ScaleModal呢?
其實很簡單,在上面基礎上,我們再寫一個ScaleModalMixinMixin類就可以了

完美的多繼承

看代碼

class BaseModal {
  close() {
    console.log('close');
  }
}

let DragModalMixin = (extendsClass) => class extends extendsClass {
  hasLayer = true;
  drag() {
    console.log('drag');
  }
};

let ScaleModalMixin = (extendsClass) => class extends extendsClass {
  scale() {
    console.log('scale');
  }
};

class CustomModal extends ScaleModalMixin(DragModalMixin(BaseModal)) {
  drag() {
    console.log('custom-drag');
  }
  do() {
    console.log('do');
  }
}

let c = new CustomModal();

c.close(); // close
c.drag(); // custom-drag
c.scale(); // scale
console.log(c.hasLayer); // true

存在的問題

這種方式不會修改父類的原型對象,但是如果存在跟父類同名的方法,只會執行父類的,而不回執行被繼承的類的方法,那麼如何使相同方法分別執行呢?

super實現相同方法不覆蓋

看代碼

class BaseModal {
  close() {
    console.log('close');
  }
}

let DragModalMixin = (extendsClass) => class extends extendsClass {
  hasLayer = true;
  drag() {
    console.log('drag');
  }
};
let ScaleModalMixin = (extendsClass) => class extends extendsClass {
  scale() {
    console.log('scale');
  }
  close() {
    console.log('scale-close');
    if (super.close) super.close();
  }
};

class CustomModal extends ScaleModalMixin(DragModalMixin(BaseModal)) {
  close() {
    console.log('custom-close');
    if (super.close) super.close();
  }
  do() {
    console.log('do');
  }
}

let c = new CustomModal();

c.close(); // custom-close   ->   scale-close   ->   close

總結

Mixin是一種思想,用來實現代碼高度可複用性,又可以用來解決多繼承的問題,是一種非常靈活的設計模式,如果你多多琢磨,相信你也會發現一些其他的妙用的,看好你喲!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章