事件绑定与类继承结合时的最佳实践

原文地址: 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 调用会返回一个全新方法, 没法用其原方法取消事件订阅
相关文章
相关标签/搜索