如何在另一实例对象的上下文中调用目标实例的方法?
解决JavaScript中回调函数上下文绑定的问题
嘿,这个问题简直是JS新手必踩的经典坑——回调函数的this指向不对!我来给你拆解清楚问题出在哪,以及几种简单的解决办法:
问题根源
你代码里的监听器函数在OnChangeManager.motionEvent()里被直接调用(listener()),这时候this的指向是当前的执行上下文,也就是OnChangeManager实例,而不是你期望的Ball实例。毕竟JS里的this是调用时绑定的,不是定义时绑定的~
另外先提两个代码里的小错误,不修复的话也会出问题:
OnChangeManager的constructor少了括号,应该是constructor(){}Ball.addMotionListener里拼写错误:_onChnangeManager→_onChangeManager,addMotionListner→addMotionListener- 遍历数组别用
for...in,它会遍历数组的原型属性,要用for...of或者普通for循环
解决方案(三种任选其一)
方案1:绑定this在添加监听器时
在game.js里给回调函数用bind()方法,把它的this固定绑定到ball实例上:
var ball = new Ball(); // 用bind把回调的this绑定到ball ball.addMotionListener(function(){ this._color = someNewRandomColor(); }.bind(ball));
bind()会返回一个新函数,这个函数的this永远是你传入的ball,不管在哪调用它。
方案2:用ES6箭头函数
箭头函数没有自己的this,它会继承外层作用域的this。在game.js里用箭头函数的话,外层作用域的this就是ball(因为是在调用ball.addMotionListener的上下文里):
var ball = new Ball(); ball.addMotionListener(() => { this._color = someNewRandomColor(); });
这个方法更简洁,适合ES6+的环境。
方案3:调用监听器时指定this
修改OnChangeManager的motionEvent方法,让它接收一个上下文参数,调用监听器时用call()指定this:
首先修改Ball类的position setter,把当前实例传进去:
set position(newP){ this._position = newP; // 把Ball实例传给motionEvent this._onChangeManager.motionEvent(this); }
然后修改OnChangeManager的motionEvent:
motionEvent(context){ // 遍历监听器队列,用call指定this为传入的context(Ball实例) for(const listener of this._motionListenerQueue){ listener.call(context); } }
这种方法的好处是不用修改调用方的代码,把上下文绑定的逻辑放在事件管理器里。
修复后的完整代码示例
game.js
// 先实现一个生成随机颜色的函数 function someNewRandomColor() { const letters = '0123456789ABCDEF'; let color = '#'; for (let i = 0; i < 6; i++) { color += letters[Math.floor(Math.random() * 16)]; } return color; } var ball = new Ball(); // 选方案1或方案2的话用下面的代码 ball.addMotionListener(() => { this._color = someNewRandomColor(); }); // 模拟kickBall修改position function kickBall(ball) { ball.position = new Vector(10, 20); } kickBall(ball);
Ball.js
Ball = class{ constructor(){ this._position = new Vector(0,0); this._color = 'red'; // 字符串要加引号,不然会被当成未定义变量 this._onChangeManager = new OnChangeManager(); } addMotionListener(listener){ this._onChangeManager.addMotionListener(listener); // 修复拼写错误 } set position(newP){ this._position = newP; // 方案1、2用这个: this._onChangeManager.motionEvent(); // 方案3用这个: // this._onChangeManager.motionEvent(this); } }
OnChangeManager.js
OnChangeManager = class{ constructor(){ // 修复括号缺失 this._motionListenerQueue = []; } addMotionListener(newListener){ this._motionListenerQueue.push(newListener); } // 方案1、2对应的motionEvent motionEvent(){ for(const listener of this._motionListenerQueue){ listener(); } } // 方案3对应的motionEvent(如果用方案3就注释掉上面的,用这个) // motionEvent(context){ // for(const listener of this._motionListenerQueue){ // listener.call(context); // } // } }
这样修改后,监听器里的this就会指向ball实例,就能正常修改它的_color属性啦!
内容的提问来源于stack exchange,提问作者Jonzi
相关产品推荐
相关产品推荐

