🌞

Mixin của JS

Sửa bài viết này

Trong JS, chúng ta chỉ có thể kế thừa từ một object, bên trong mỗi object chỉ chứa duy nhất một property [[Prototype]], và một class thì chỉ extends từ một class khác.

Đôi khi như vậy khá giới hạn, điều này đẻ ra khái niệm mixins, cho phép một class có thể sử dụng các phương thức của một class khác mà không cần kế thừa

let sayHiMixin = {
    sayHi() {
        alert(`Hello ${this.name}`);
    },
    sayBye() {
        alert(`Bye ${this.name}`);
    }
}

// Sử dụng
class User {
    constructor(name) {
    	this.name = name;   
    }
}

Object.assign(User.prototype, sayHiMixin)
new User("Andy").sayHi();

Chúng ta không kế thừa, mà đơn giản là merge tất tả các phương thức lại thông qua prototype

Bản thân một mixin cũng có thể kế thừa từ một mixin khác

let sayMixin = {
    say(phrase) {
        alert(phrase);
    }
}

let sayHiMixin = {
    // hoặc sử dụng Object.setPrototypeOf
    __proto__: sayMixin,    
    sayHi() {
        // gọi đến phương thức từ mixin kế thừa
        super.say(`Hello ${this.name}`);    },
    sayBye() {
        super.say(`Bye ${this.name}`); // (*)
    }
}

super sẽ gọi đến phương thức kế thừa từ cha

Ứng dụng mixin vào thực tế, khai báo một EventMixin để tất cả các function/class/object có thể sử dụng truyền đi một thông tin nào đó

  • trigger bắn sự kiện và các thông tin đính kèm
  • on subcribe một handler với một sự kiện
  • off unsubscribe khỏi sự kiện
let eventMixin = {
    /**
    * subcribe trên sự kiện
    * sử dụng menu.on('select', function(item) { ... })
    */
    on(eventName, handler) {
        if (!this._eventHandlers) this._eventHandlers = {};
        if (!this._eventHandlers[eventName]) {
            this._eventHandlers[eventName] = [];
        }
        this._eventHandlers[eventName].push(handler);
    },
    
    /**
    * unsubcribe
    * menu.off('select', handler)
    */
    off(eventName, handler) {
        let handlers = this._eventHandlers?.[eventName];
        if (!handlers) return;
        
        for(let i = 0; i < handlers.length; i++) {
            if(handlers[i] === handler) {
                handlers.splice(i--, 1)
            }
        }
    },
    
    /**
    * Trigger event
    * this.trigger('select', data1, data2)
    */
    trigger(eventName, ...args) {
        if (!this._eventHandlers?.[eventName]) {
            return;
        }
        
        // gọi handler
        this._eventHandlers[eventName].forEach(
            handler => handler.apply(this, args)
        )
    }
}

Sử dụng

class Menu {
  choose(value) {
    this.trigger("select", value);
  }
}

Object.assign(Menu.prototype, eventMixin);

let menu = new Menu();

menu.on("select", value => alert(`Value selected: ${value}`));
menu.choose("123");

Tóm ý

  • mixin là một phương pháp trong object-oriented programming để cho phép một class không cần extends nhưng vẫn có thể sử dụng các phương thức từ class khác
  • Các ngôn ngữ khác có thể cho phép đa kế thừa, tuy nhiên điều này không được trong js, phải dùng mixin
  • Vấn để của mixin là sẽ ghi đè những phương thức trùng tên