原文地址: 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
是定義在派生類的原型鏈上, 實例屬性訪問優先級大於原型鏈, 因此根本沒執行到派生類的 handleClick
git
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
}
}
複製代碼