用setInterval调用的方法将Javascript对象属性报告为未定义

用setInterval调用的方法将Javascript对象属性报告为未定义,javascript,methods,this,setinterval,Javascript,Methods,This,Setinterval,我正试着用Javascript来概括我的想法,我想制作一个简单的web小程序,它可以通过按钮来启用和禁用,并且在启用时,可以绘制一些东西。为了编写干净的(er)代码,我想为此使用一个对象。带有按钮的页面设置为 //... javascript代码是 函数游戏(){ this.runs=false; this.run=function(){console.log('run…');this.runs=true;}; this.reset=function(){console.log('reset

我正试着用Javascript来概括我的想法,我想制作一个简单的web小程序,它可以通过按钮来启用和禁用,并且在启用时,可以绘制一些东西。为了编写干净的(er)代码,我想为此使用一个对象。带有按钮的页面设置为


//...
javascript代码是

函数游戏(){
this.runs=false;
this.run=function(){console.log('run…');this.runs=true;};
this.reset=function(){console.log('reset…');this.runs=false;};
this.update=function(){console.log('update…runs:',this.runs);};
};
var game=新游戏();
game.reset();
设置间隔(game.update,300);
所以它是一个对象定义(游戏),一个实例(游戏)有一个布尔属性(运行)和三个方法。一个运行它,一个停止运行,还有一个update()方法报告它是否运行。使用setInterval每隔300毫秒重复更新()

问题:控制台从update()日志中报告this.runs的值未定义,而不是false或true。当我打开控制台并暂停它以检查变量时,它会正确地报告game.runs为false或true。另外,当我向run()和reset()添加console.log()调用时,会在设置之前和之后报告this.runs的值,它似乎会正确地报告true和false。所以问题似乎出在update()中的某个地方。就好像它用错了“this”。可能无法在方法上使用setInterval

我为代码尝试了另外两种语法,但它们似乎有完全相同的问题:

var游戏={
跑步:错,
run:function(){console.log('run…');this.runs=true;},
reset:function(){console.log('reset…');this.runs=false;},
update:function(){console.log('update…runs:',this.runs);}
};
game.reset();
设置间隔(game.update,300);
以及在对象内设置setInterval的版本:

var游戏={
跑步:错,
i:未定义,
run:function(){console.log('run…');this.runs=true;this.i=setInterval(this.update,300);},
reset:function(){console.log('reset…');this.runs=false;clearInterval(this.i);},
update:function(){console.log('update…runs:',this.runs);}
};
game.reset();
同样的问题


发生了什么事?为什么update()会将此运行报告为未定义?在所有情况下,方法中的“this”确实指的是游戏实例,这是正确的吗?我是否应该在方法上使用setInterval,而不是调用全局函数?

当您使用以下语法定义内部函数时:
function(){}
,则此函数将有自己的
this
,因此
this.runs
将是未定义的,如果您希望它成为父函数的对象,您有两个选择:

选项1:将内部函数定义为箭头函数:

函数游戏(){
this.runs=false;
this.run=()=>{console.log('run…');this.runs=true;};
this.reset=()=>{console.log('reset…');this.runs=false;};
this.update=()=>{console.log('update…runs:',this.runs);};
};
var game=新游戏();
game.reset();
设置间隔(game.update,300)

因为
的上下文不再是
游戏
game.update
被调用为对
setInterval
的回调,如果使用箭头函数,它将解决问题

函数游戏(){
self=这个
this.runs=false;
this.run=function(){console.log('run…');self.runs=true;};
this.reset=function(){console.log('reset…');self.runs=false;};
//使用arrow函数()=>{}代替普通函数
this.update=()=>{console.log('update…runs:',self.runs);};
};
var game=新游戏();
game.reset();

设置间隔(game.update,300)在JavaScript中,此
的规则有些复杂;相关的是,如果作为方法调用,则存储在对象属性中的非箭头函数可以将
this
分配给对象。让我们分析一下:

  • game.update
    game
    对象的属性,✅
  • 它包含一个非箭头函数,✅
  • 它被作为一个方法调用。。。❌
“作为方法调用”是什么意思?这意味着您在
object.property
语法上调用函数,如下所示:
game.update(…)

但是,
game.update
作为一个参数传递,它将失去与
game
的连接。您的代码相当于:

var func = game.update;
setInterval(func, 300);
其中
setTimeout
将只调用
func()
。这意味着
game.update
是作为函数调用的,而不是作为方法调用的,
This
在调用时不会设置为
game

典型的解决办法是:

  • 将接收器绑定到函数。这是在上述方法调用之外设置
    This
    的另一种方法:如果函数绑定到接收方对象,则在调用时它将始终将
    This
    设置到该对象。你可以这样写:

    setInterval(game.update.bind(game), 300)
    
    React中经常使用的一种变体是在定义位置将函数显式绑定到接收器:

    this.update = function() {console.log('updating... runs:', this.runs);};
    this.update = this.update.bind(this);
    
  • 通过以下任一方式显式使用方法调用:

    setInterval(() => game.update(), 300);
    setInterval(function() { game.update(); }, 300);
    
  • 通过使用箭头函数对该
    进行词汇定义。由于
    是定义函数时的游戏对象,因此将其设置为箭头函数将始终将
    设置为该游戏对象。这需要在定义点而不是调用点进行更改:

    this.update = () => {console.log('updating... runs:', this.runs);};