JS:如何将上下文传递给访问函数的 setTimeOut 匿名函数 class

JS: How to pass context to setTimeOut anonymous function which accesses a function class


这是我第一次在 Stack Overflow 上发帖;我的知识刚好足以玩 Javascript,但我写的所有东西都有一个错误。如果我的术语有误,请原谅我。

我刚刚了解到setTimeOut 可以用来创建多个定时器。所以,我写了一个函数 class 来创建带有自己计时器的对象。但是当我通过该函数 class 的方法调用 setTimeout() 时,传递一个匿名函数,它似乎没有适当的上下文。我该如何解决这个问题?

我已经通过在函数 class 中编写我想要执行的函数来解决这个问题,但这剥夺了它的灵活性。

下面是我的(不太漂亮的)工作代码片段:

函数class声明:

function sal_clock2 () {
 this.initialized = false;
 var aTimerObject = {timerStarted: false};
 this.holdTimers = [];
 var mythis = this; 
 this.checkIfNotInit = function(index) {
    if (typeof this.holdTimers[index] === "undefined") { //if timer doesn't exist then create it.
      this.holdTimers[index] = {timerStarted: false};
    }
};
 this.setTimeout = function(runFunction, millisecondsDelay) { 
/*runFunction is what I want it to do. Right now it only takes an array index.*/
    //I need the right context so I provide custom "this" by means of "mythis"
    var IDofTimer = setTimeout(function(){mythis.holdTimers[runFunction].timerStarted = false;}, millisecondsDelay);
    return IDofTimer; //returning IDofTimer so that I can reference it later to stop timer if necessary.
}; //end this.setTimeOut

}

sal_clock2class 的用法,当试图设置“敌人对玩家造成伤害”间隔时:

var t = new sal_clock2();


for (i = 0; i < $dataMap.events.length; i++) {//do loop to find all enemies so they can attack
if ($dataMap.events[i]) { 
    if ($dataMap.events[i].meta.enemy == "1") { //do enemy attack
        if ($gameMap._events[i]._characterName !== ""){
        if ($gamePlayer._y-1 == $gameMap._events[i]._y && $gamePlayer._x == $gameMap._events[i]._x || // If x is same, and above enemy
            $gamePlayer._y+1 == $gameMap._events[i]._y && $gamePlayer._x == $gameMap._events[i]._x || // If x is same, and below enemy
            $gamePlayer._x-1 == $gameMap._events[i]._x && $gamePlayer._y == $gameMap._events[i]._y || // If y is same, and left of enemy
            $gamePlayer._x+1 == $gameMap._events[i]._x && $gamePlayer._y == $gameMap._events[i]._y) { // If y is same, and right of enemy
                if (!$gameMap.event(i)._sal_unconscious) {//if enemy is not "dead"              

                t.checkIfNotInit(i); //this seems to successfully setup timer for use.

                    if (t.holdTimers[i].timerStarted == false) {
                        t.holdTimers[i].timerStarted = true;
                        t.setTimeout(i,3000); //**//this is my custom setTimeout**
                        //t.setTimeout calls a function defined in function class constructor "sal_clock2", 
                        //which calls JS's setTimeout()
                        $gameActors._data[1]._hp -= 20;
                        $gamePlayer.requestAnimation(1);
                    } //end 
                } //end sal_unconscious check
            } //end coordinate check
        }
    }//end if 
}

}

首选代码调用(反对t.setTimeout(i,3000)):

t.setTimeoutTest(function(){t.holdTimers[i].timerStarted = false;}, 3000);

我的问题是,与其在函数 class 构造函数中进行函数声明,不如在上面找到的碰撞“for 循环”中进行。但是传递匿名函数不允许我访问 t.holdTimers[i].timerStarted = true; 这样我就可以通过 requestAnimationFrame 阻止函数重复,而不是只重复在我指定的时间间隔。

请帮忙,
无知的码农

编辑:
我很抱歉不清楚。我已经上传了一些与我的碰撞代码分开的代码,并将其发布在 github 上。它只有最基本的输出值,一个工作示例和一个不工作的示例。在这里查看:github

我已经在匿名函数上尝试了 .bind(this),但没有成功。将对象引用“t”传递给匿名函数也不成功。我什至无法使用命名函数。

您在错误的范围内设置了 mythis。与 Java 和 C++ 等其他语言一样,javascript 中的局部变量在其定义的函数范围之外不可访问。因此在正确的范围内定义 mythis

function sal_clock2 () {
 this.initialized = false;
 var aTimerObject = {timerStarted: false};
 this.holdTimers = [];
 // var mythis = this; // <----- WRONG PLACE, remove this
 this.checkIfNotInit = function(index) {
    if (typeof this.holdTimers[index] === "undefined") { //if timer doesn't exist then create it.
      this.holdTimers[index] = {timerStarted: false};
    }
};


this.setTimeout = function(runFunction, millisecondsDelay) { 

    var mythis = this; // <----- you need to capture `this` HERE

    var IDofTimer = setTimeout(function(){mythis.holdTimers[runFunction].timerStarted = false;}, millisecondsDelay);
    return IDofTimer; //returning IDofTimer so that I can reference it later to stop timer if necessary.
}; //end this.setTimeOut

但是,现代 javascript 具有您可以用来避免的功能 this aliasing/caching:

箭头函数静态捕获作用域:

this.setTimeout = function(runFunction, millisecondsDelay) { 

    // PASS AN ARROW FUNCTION to setTimeout:

    var IDofTimer = setTimeout(() => {
        this.holdTimers[runFunction].timerStarted = false; // no need mythis
    }, millisecondsDelay);

    return IDofTimer;

};

函数也可以手动绑定到特定的 this:

this.setTimeout = function(runFunction, millisecondsDelay) {

    var callback = function () {
        this.holdTimers[runFunction].timerStarted = false; // no need mythis
    }

    // Manually binding the callback function allow you to pass any `this`
    // you want to it:
    var IDofTimer = setTimeout(callback.bind(this), millisecondsDelay);

    return IDofTimer;

};

感谢那些对我的编码问题提出见解的人,我已经找出了需要在我的代码中修复的内容。我对 scope、th​​is 和 bind 的理解需要一些调整。

首先,我的匿名函数需要使用“this”。

t.setTimeoutTest(function() {this.holdTimers[i].timerStarted = false;}, 3000);

这似乎误导了我,因为“this”会有错误的上下文,直到我在函数 class 的 setTimetOutTest 函数中尝试以下操作:

this.setTimeoutTest = function(runFunction, millisecondsDelay) {
     var IDofTimer = setTimeout(runFunction.bind(this), millisecondsDelay);
}

如果这就是它的结尾,我会把上面的答案标记为正确的。但我就没那么幸运了。

使用此代码会导致导致我的应用程序崩溃的错误。我终于意识到我的 for 循环被编程为 7 次迭代,但错误在第 8 次崩溃。似乎(在我 un-expert 看来)第 7 次迭代完成后,它便将索引递增到第 8 次。这是一个问题,因为我使用索引来引用变量,但是在 t.setTimeout() 调用函数时变量已经递增到数组中的未定义元素,导致我的代码崩溃。

我通过将索引保存到一个变量并在我的匿名函数中使用它来解决这个问题。

var myI = i;
t.setTimeoutTest(function() {this.holdTimers[myI].timerStarted = false;}, 3000);

现在,我的代码按预期工作并且更健壮,尽管有点不干净。谢谢大家!