如何在addEventListener函数中将对象实例绑定到this关键字
我完全懂你现在的困扰——用全局变量来引用实例确实既不优雅又有隐患,尤其是以后要支持多实例的时候,肯定会出现引用冲突的问题。咱们先拆解一下你之前的错误尝试,再一步步解决这个this绑定的问题。
首先看你之前的错误代码:你把bind(this)加在了addEventListener的返回值上,但addEventListener本身返回的是undefined,所以这个bind根本没作用在回调函数上,这就是为什么它没生效。正确的做法是把bind绑定在回调函数本身,或者用其他更简洁的方式来固定this的指向。
下面给你三种常用且靠谱的解决方案:
方案1:使用箭头函数(最推荐,ES6+)
箭头函数的特性就是不会创建自己的this,它会继承外层作用域的this——在你的构造函数里,外层作用域的this就是当前的编辑器实例。这样回调里的this自然就指向实例,直接调用this.toggle()就行:
class editor { constructor() { this.element = document.createElement("div"); // 给元素加个样式方便测试 this.element.style.width = "200px"; this.element.style.height = "200px"; this.element.style.background = "#eee"; this.element.textContent = "编辑器元素"; // 箭头函数继承构造函数的this addEventListener("keydown", (event) => { if(event.key == "`") { this.toggle(); } }); } toggle() { if(document.body.contains(this.element)) { document.body.removeChild(this.element); } else { document.body.appendChild(this.element); } } } // 现在可以创建多个实例,每个实例的事件都独立触发自己的toggle const edit1 = new editor(); const edit2 = new editor();
方案2:正确使用bind绑定回调
如果习惯用普通函数作为回调,那就要把bind(this)直接调用在回调函数上,这样才能把回调的this绑定到当前实例:
class editor { constructor() { this.element = document.createElement("div"); this.element.style.width = "200px"; this.element.style.height = "200px"; this.element.style.background = "#eee"; // 把bind绑定在回调函数本身 addEventListener("keydown", function(event) { if(event.key == "`") { this.toggle(); } }.bind(this)); // 这里的bind作用在回调函数上,而不是addEventListener的返回值 } toggle() { if(document.body.contains(this.element)) { document.body.removeChild(this.element); } else { document.body.appendChild(this.element); } } } const edit = new editor();
方案3:提前保存this到变量(ES6之前常用)
这种方法很直观:在构造函数里把实例的this保存到一个变量(比如self),然后在回调里用这个变量来访问实例方法,避免普通函数回调里this指向全局对象(或严格模式下的undefined)的问题:
class editor { constructor() { this.element = document.createElement("div"); this.element.style.width = "200px"; this.element.style.height = "200px"; this.element.style.background = "#eee"; // 保存实例的this到self变量 const self = this; addEventListener("keydown", function(event) { if(event.key == "`") { self.toggle(); // 用self代替this,指向实例 } }); } toggle() { if(document.body.contains(this.element)) { document.body.removeChild(this.element); } else { document.body.appendChild(this.element); } } } const edit = new editor();
额外提醒:别忘了移除事件监听器
如果你的编辑器实例会被销毁(比如关闭页面或删除组件),最好在类里加一个destroy方法来移除事件监听器,避免内存泄漏。这里以箭头函数为例,需要先把回调保存到实例上:
class editor { constructor() { this.element = document.createElement("div"); this.element.style.width = "200px"; this.element.style.height = "200px"; this.element.style.background = "#eee"; // 把回调保存到实例属性 this.keyboardCallback = (event) => { if(event.key == "`") { this.toggle(); } }; addEventListener("keydown", this.keyboardCallback); } toggle() { if(document.body.contains(this.element)) { document.body.removeChild(this.element); } else { document.body.appendChild(this.element); } } destroy() { // 移除事件监听器 removeEventListener("keydown", this.keyboardCallback); // 同时移除元素 if(document.body.contains(this.element)) { document.body.removeChild(this.element); } } }
这样不管你创建多少个实例,每个实例的事件回调都会正确绑定到自己,再也不需要依赖全局变量啦!
备注:内容来源于stack exchange,提问作者MirceaKitsune

