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

时间:2019-05-15 08:00:49

标签: javascript methods this setinterval

我试图用Java语言包扎头,想制作一个简单的Web小程序,该小程序可以通过按钮启用和禁用,并且在启用时会绘制东西。为了编写更干净的代码,我想为此使用一个对象。使用按钮的页面设置为

<div>
    <input type="button" name="runbutton"   value="Run"   onclick="game.run();"/>
    <input type="button" name="resetbutton" value="Reset" onclick="game.reset();"/>
</div>

<script>
    //...
</script>

并且javascript代码是

function Game() {
    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('updating... runs:', this.runs);};
};
var game = new Game();
game.reset();
setInterval(game.update, 300);

因此,这是一个对象定义(游戏),其中一个实例(游戏)具有一个布尔属性(运行)和三个方法。一个运行它,一个停止运行,以及一个update()方法报告它是否运行。使用setInterval每隔300 ms重复执行一次update()。

问题:控制台从update()日志中报告this.runs的值未定义,而不是false或true。当我打开控制台并将其暂停以检查变量时,它将把game.runs正确报告为false或true。另外,当我将console.log()调用添加到run()并reset()来报告this.runs的值,然后再对其进行设置之后,似乎可以正确地报告是非。因此,问题似乎出在update()中。好像使用了错误的“ this”。也许setInterval不能在方法上使用?

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

var game = {
    runs: false,
    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();
setInterval(game.update, 300);

以及在对象内设置setInterval的版本:

var game = {
    runs: false,
    i: undefined,
    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.runs报告为未定义?我是否正确认为方法中的“ this”在所有情况下确实均指游戏实例?我不应该在方法上使用setInterval而是调用全局函数吗?

3 个答案:

答案 0 :(得分:1)

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

  • game.updategame对象✅
  • 的属性
  • 它包含一个非箭头函数✅
  • 它被作为方法调用...❌

“作为方法调用”是什么意思?这意味着您使用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。由于this是定义功能的游戏对象,因此将它们变成箭头功能将始终将this设置为该游戏对象。这需要在定义时进行更改,而不是在调用时进行更改:

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

答案 1 :(得分:0)

当您使用以下语法定义内部函数时:function() {},则此函数将具有其自己的this,因此,如果希望将此this.runs用作对象的对象,则function Game() { this.runs = false; this.run = () => {console.log('run...'); this.runs = true;}; this.reset = () => {console.log('reset...'); this.runs = false;}; this.update = () => {console.log('updating... runs:', this.runs);}; }; var game = new Game(); game.reset(); setInterval(game.update, 300);将是未定义的。父函数,您有两个选择:

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

<div>
    <input type="button" name="runbutton"   value="Run"   onclick="game.run();"/>
    <input type="button" name="resetbutton" value="Reset" onclick="game.reset();"/>
</div>
this

选项2::将父函数function Game() { self = this this.runs = false; this.run = function() {console.log('run...'); self.runs = true;}; this.reset = function() {console.log('reset...'); self.runs = false;}; this.update = function() {console.log('updating... runs:', self.runs);}; }; var game = new Game(); game.reset(); setInterval(game.update, 300);存储为变量

<div>
    <input type="button" name="runbutton"   value="Run"   onclick="game.run();"/>
    <input type="button" name="resetbutton" value="Reset" onclick="game.reset();"/>
</div>
declare
  pl_query                varchar2(4000);
  cl_collectie   constant varchar2(255) := 'MY_COLLECTION';
begin
  --
  if apex_collection.collection_exists( p_collection_name => cl_collectie )
  then
    apex_collection.delete_collection( p_collection_name => cl_collectie );
  end if;
  --
  pl_query := q'[select QUERY]' ;
  --       
  apex_collection.create_collection_from_queryb2( p_collection_name => cl_collectie
                                                , p_query => pl_query );
end;         

答案 2 :(得分:0)

由于this的上下文不再是game,因此game.update被称为setInterval的回调,如果使用箭头功能将解决此问题。 / p>

function Game() {
    self = this
    this.runs = false;
    this.run = function() {console.log('run...'); self.runs = true;};
    this.reset = function() {console.log('reset...'); self.runs = false;};
    // Using arrow function () => {} instead of normal function
    this.update = () => {console.log('updating... runs:', self.runs);};
};
var game = new Game();
game.reset();
setInterval(game.update, 300);

详细了解箭头功能 this here