事件綁定與類繼承結合時的最佳實踐

原文地址: github.com/yinxin630/b…
技術交流羣: fiora.suisuijiang.com/javascript

事件綁定和類繼承都是很經常使用的東西, 當它倆結合起來時, 可能並不會像你所想的那樣工做html

來看一個最簡單的例子, 在構造函數中綁定 click 事件, 點擊後打印 "click"this.a
在該例中 this.a 會打印什麼呢? 會打印 undefined, 由於 handleClick 的 this 指向是 button dom 對象, dom 對象沒有 a 屬性java

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
    </head>
    <body>
        <button id="click">click</button>
        <script> class Base { a = 1; constructor() { document.querySelector("#click").addEventListener("click", this.handleClick); } handleClick() { console.log("click", this.a); // click undefined } } new Base(); </script>
    </body>
</html>
複製代碼

首先, 能夠用箭頭函數來解決 this 指向問題, 在 react 中這種寫法很常見, 這沒什麼問題react

handleClick = () => {
    console.log("click", this.a); // click 1
};
複製代碼

可是, 當與類繼承相結合時會怎樣呢? 以下的例子中, 派生類繼承基類並重載 handleClick 方法
點擊後會並不會輸出 "click2", 由於基類的 handleClick 是定義在實例屬性上, 而派生類的 handleClick 是定義在派生類的原型鏈上, 實例屬性訪問優先級大於原型鏈, 因此根本沒執行到派生類的 handleClickgit

class Base {
    a = 1;
    constructor() {
        document.querySelector("#click").addEventListener("click", this.handleClick);
    }
    handleClick = () => {
        console.log("click", this.a); // click 1
    };
}
class Derived extends Base {
    handleClick() {
        super.handleClick();
        console.log("click2", this.a); // not run
    }
}
new Derived();
複製代碼

嘗試經過原型鏈直接調用派生類的 handleClick, 注意! 因爲是直接調用的, super.handleClick() 不可用須要註釋掉
會輸出 click2 1, 可是不會調到基類方法github

class Base {
    a = 1;
    handleClick = () => {
        console.log("click", this.a); // not run
    };
}
class Derived extends Base {
    handleClick() {
        // super.handleClick();
        console.log("click2", this.a); // click2 1
    }
}
const ins = new Derived();
Derived.prototype.handleClick.call(ins);
複製代碼

修改一下, 將基類改成普通函數, 並在綁定事件時 bind this, 這就是咱們所指望的效果了npm

class Base {
    a = 1;
    constructor() {
        document.querySelector("#click").addEventListener("click", this.handleClick.bind(this));
    }
    handleClick() {
        console.log("click", this.a); // click 1
    }
}
class Derived extends Base {
    handleClick() {
        super.handleClick();
        console.log("click2", this.a); // click2 1
    }
}
複製代碼

接下來, 咱們增長一個需求, 新增一個按鈕用來取消事件訂閱
以下所示, 點擊 unsubscribe 按鈕後調用 removeEventListener 取消事件訂閱, 可是並不起做用(包括註釋那行)
爲何呢? 由於訂閱和取消訂閱的並非同一個方法, 訂閱時的 bind 調用會返回一個全新函數, 因爲沒有保存該函數引用, 調用 removeEventListener 也就沒法將其取消訂閱
怎麼解決呢?dom

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
    </head>
    <body>
        <button id="click">click</button>
        <br />
        <button id="unsubscribe">unsubscribe</button>
        <script> class Base { a = 1; constructor() { document.querySelector("#click").addEventListener("click", this.handleClick.bind(this)); document.querySelector("#unsubscribe").addEventListener("click", () => { // 沒法取消訂閱 document.querySelector("#click").removeEventListener("click", this.handleClick.bind(this)); // document.querySelector("#click").removeEventListener("click", this.handleClick); }); } handleClick() { console.log("click", this.a); } } class Derived extends Base { handleClick() { super.handleClick(); console.log("click2", this.a); } } new Derived(); </script>
    </body>
</html>
複製代碼

只要將 bind 後的實例保存下來便可, 這樣就能確保訂閱和取消訂閱的是同一方法了, 完美達成指望函數

constructor() {
    this.handleClick = this.handleClick.bind(this); // 保存 bind 後方法
    document.querySelector('#click').addEventListener('click', this.handleClick);
    document.querySelector('#unsubscribe').addEventListener('click', () => {
        // 能夠取消訂閱
        document.querySelector('#click').removeEventListener('click', this.handleClick);
    });
}
複製代碼

還能夠用 (www.npmjs.com/package/aut…) 裝飾器自動完成 bind 操做ui

class Base {
    a = 1;
    constructor() {
        document.querySelector("#click").addEventListener("click", this.handleClick.bind(this));
    }
    @autobind // 裝飾器
    handleClick() {
        console.log("click", this.a); // click 1
    }
}
class Derived extends Base {
    @autobind
    handleClick() {
        super.handleClick();
        console.log("click2", this.a); // click2 1
    }
}
複製代碼

總結

  1. 用箭頭函數解決 this 綁定問題時, 該方法(實際上是屬性)沒法被重載
  2. bind 調用會返回一個全新方法, 沒法用其原方法取消事件訂閱
相關文章
相關標籤/搜索