如何解决事件回调中this指向DOM与类实例的冲突
jQuery的事件绑定逻辑默认会把回调函数内的this指向触发事件的DOM元素。你直接将this.selectButton作为回调传入.on()时,本质是把类原型上的方法单独抽离出来传参,方法执行时完全丢失了和Passwords类实例的绑定关系。此时selectButton内的this就是被点击的按钮DOM对象,DOM上不存在columnController方法,自然会抛出类型错误。
你需要同时拿到「被点击的DOM元素」和「类实例上下文」,以下三种写法都可以满足需求,根据自己的代码习惯选就行:
方案1:箭头函数包裹回调(最直观无副作用)
用箭头函数包裹一层回调,箭头函数会继承构造函数内的类实例this,被点击的DOM元素通过事件对象的currentTarget属性获取,不需要改动原有方法的上下文绑定逻辑:
class Passwords { constructor() { $('.password-column button.steel-button').on('click', (e) => { // 这里的this始终是Passwords类实例 this.selectButton(e.currentTarget); }); } selectButton(clickButton) { // 入参就是被点击的按钮DOM,直接使用即可 $(clickButton).toggleClass('selected'); this.columnController(); } columnController(){ // 实例方法可正常调用 } }
方案2:bind固定方法上下文
如果不想额外包裹回调,可以用Function.prototype.bind提前把selectButton方法的this永久绑定到当前类实例,DOM元素同样从事件对象上取:
class Passwords { constructor() { // 绑定后的方法this固定指向当前类实例 $('.password-column button.steel-button').on('click', this.selectButton.bind(this)); } selectButton(e) { // e.currentTarget等价于jQuery默认回调里的this,指向绑定事件的按钮 $(e.currentTarget).toggleClass('selected'); this.columnController(); } columnController(){ // 实例方法可正常调用 } }
方案3:类字段箭头函数(适合多场景复用方法)
如果selectButton需要在多处作为回调传递,可以直接把它定义为类的箭头函数字段,这类字段的this会在实例初始化时永久绑定到类实例,不需要每次调用都手动绑定:
class Passwords { constructor() { $('.password-column button.steel-button').on('click', this.selectButton); } // 箭头函数字段,this始终指向类实例 selectButton = (e) => { $(e.currentTarget).toggleClass('selected'); this.columnController(); } columnController(){ // 实例方法可正常调用 } }
避坑提醒:获取点击元素时优先使用
e.currentTarget,不要用e.target。如果按钮内部嵌套了文本、图标等子元素,点击时e.target会指向你实际命中的子节点,和你预期的按钮元素不符;e.currentTarget会始终指向绑定了点击事件的按钮元素,和jQuery默认注入到回调中的this指向完全一致。
内容的提问来源于stack exchange,提问作者Dylan B

