什麼是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
能夠同時繼承DragModal
和ScaleModal
? - 某個相同方法希望不覆蓋,而是都執行
試試早期的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是一種思想,用來實現程式碼高度可複用性,又可以用來解決多繼承的問題,是一種非常靈活的設計模式,如果你多多琢磨,相信你也會發現一些其他的妙用的,看好你喲!