`this` 的属性在 setTimeout 中未定义

问题描述 投票:0回答:3
class Simulator {
  constructor() {
    this.gates = Array();
    this.gates.push(new AndGate(200, 200));
  }
  initialize() {
    let canvas = document.getElementById('board');
    canvas.width = 800;
    canvas.height = 500;
    canvas.setAttribute("style", "border: 1px solid black");
    this.gates.push(new AndGate(100, 100));
  }
  run() {
    setTimeout(this.onLoop, 1000);
  }
  onLoop() {
    for (let gate of this.gates) {
      gate.render();
    }
  }
}
let sim = new Simulator();
sim.initialize();
sim.run();

由于某种原因,我的 TypeScript 类的 JS 转译版本在

onLoop
函数中抛出错误。它报告
TypeError: this.gates is undefined
。但是,如果我访问
sim
(模拟器对象)并手动访问它定义的 Gates 属性。我可以从控制台手动运行 onLoop 代码。

javascript this
3个回答
6
投票

当函数通过引用传递时,它们会失去对

this
的引用。致电
setTimeout
时您将丢失此参考信息。

函数有一个

bind()
方法,基本上返回一个新函数,并带有对
this
的正确引用。

这样称呼它:

setTimeout(this.onLoop.bind(this), 1000)

或者,您也可以传递内联箭头函数。箭头函数不会丢失其

this
上下文。

setTimeout(() => this.onLoop(), 1000)

2
投票

当在

this.onLoop
内部调用
setTimeout
时,
onLoop
内部的调用上下文是window,因为
setTimeout
window对象的函数。您可以通过使用 calls
onLoop
的箭头函数来解决此问题,而不是直接传递
onLoop

class Simulator {
  constructor() {
    this.gates = Array();
    //this.gates.push(new AndGate(200, 200));
  }
  initialize() {
    //let canvas = document.getElementById('board');
    //canvas.width = 800;
    //canvas.height = 500;
    //canvas.setAttribute("style", "border: 1px solid black");
    // this.gates.push(new AndGate(100, 100));
    this.gates.push({ render: () => console.log('rendered') });
  }
  run() {
    setTimeout(() => this.onLoop(), 1000);
  }
  onLoop() {
    for (let gate of this.gates) {
      gate.render();
    }
  }
}
let sim = new Simulator();
sim.initialize();
sim.run();

或者通过将

this
函数的
onLoop
上下文绑定到实例化对象:

class Simulator {
  constructor() {
    this.gates = Array();
    //this.gates.push(new AndGate(200, 200));
  }
  initialize() {
    //let canvas = document.getElementById('board');
    //canvas.width = 800;
    //canvas.height = 500;
    //canvas.setAttribute("style", "border: 1px solid black");
    // this.gates.push(new AndGate(100, 100));
    this.gates.push({ render: () => console.log('rendered') });
  }
  run() {
    setTimeout(this.onLoop.bind(this), 1000);
  }
  onLoop() {
    for (let gate of this.gates) {
      gate.render();
    }
  }
}
let sim = new Simulator();
sim.initialize();
sim.run();


0
投票

劫持这个问题(因为它是第一个在搜索“setTimeout this undefined”时弹出的问题)来发布我遇到的类似问题,该问题适合标题,但不适合问题的内容。

问题

我有这个 Foo 类,我需要等待涉及 Foo 成员的条件。

class Foo {
    constructor(bar) { this.bar = bar; }
    
    async async_increment_bar_1(new_bar) {  

        const poll = resolve => {
            if (this.bar === new_bar) {
                resolve();
            } else {
                this.bar += 1;
                setTimeout(_ => poll(resolve), 1000, new_bar);
            }
        };
        
        await new Promise(poll);
    }
}

第一种方法工作得很好,但是我发现第二种方法更简洁,而且它也不会用

poll()
函数污染命名空间。

class Foo {
    constructor(bar) { this.bar = bar; }

    async async_increment_bar_2(new_bar) {

        await new Promise(function poll(resolve) {
            if (this.bar === new_bar) { // <-- this will raise error
                resolve();
            } else {
                this.bar += 1;
                setTimeout(_ => poll(resolve), 1000, new_bar);
            }
        });
    }
}

但是,这会引发以下错误:

TypeError: Cannot read properties of undefined (reading 'bar')

我该如何解决这个问题?

回答

根据最上面的答案:

当函数通过引用传递时,它们会失去对 this 的引用。调用 setTimeout 时您将丢失此引用。

函数有一个 bind() 方法,它基本上返回一个新函数,并带有对此的正确引用。

要修复第二个代码片段,需要在通过引用传递

this
函数的每个实例中将
poll()
引用绑定到
poll()
函数;无论是在定义时还是在
setTimeout
lambda 内部再次调用时。

class Foo {
    constructor(bar) { this.bar = bar; }

    async async_increment_bar_2(new_bar) {

        await new Promise(function poll(resolve) {
            if (this.bar == new_bar) {
                resolve();
            } else {
                this.bar += 1;
                setTimeout(_ => poll.bind(this)(resolve), 1000, new_bar);
            }
        }.bind(this));
    }
}

完整代码:

class Foo {
    constructor(bar) { this.bar = bar; }
    
    async async_increment_bar_1(new_bar) {  

        const poll = resolve => {
            if (this.bar === new_bar) {
                resolve();
            } else {
                this.bar += 1;
                setTimeout(_ => poll(resolve), 1000, new_bar);
            }
        };
        
        await new Promise(poll);
    }
    
    async async_increment_bar_2(new_bar) {

        await new Promise(function poll(resolve) {
            if (this.bar == new_bar) {
                resolve();
            } else {
                this.bar += 1;
                setTimeout(_ => poll.bind(this)(resolve), 1000, new_bar);
            }
        }.bind(this));
    }
}

main = async () => {
    
    let foo = new Foo(0);
    
    console.log(`Initially foo.bar is ${foo.bar}`);
    
    await foo.async_increment_bar_2(3);
    
    console.log(`Finally foo.bar is ${foo.bar}`);
}

main();
© www.soinside.com 2019 - 2024. All rights reserved.