原文地址: 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
}
}
复制代码