2

尝试在对象中使用 setInterval 时出现一些奇怪的行为。

这是我的代码:

var Person = {
    speech: null,

    tryToSpeak: function ()
    {
        this.speech = "hello";
        self.setTimeout (this.speak, 1000);
    },

    speak: function ()
    {
        // prints out undefined
        console.log (this.speech);
    }
}

Person.tryToSpeak ();

speak()通过setTimeout()它运行时,它无权访问任何对象数据,例如speech. 这到底是怎么回事?这是不可避免的行为吗?

4

5 回答 5

3

这些方法不携带对象信息。您可以使用.bind...将该方法绑定到对象:

window.setTimeout(this.speak.bind( this ), 1000);

阅读有关javascript this 关键字的更多信息

于 2012-11-26T18:23:24.967 回答
2

先说一些注意事项:

  • 一般来说,javascript 中的约定是为“类”定义保留大写名称,这些定义将使用new关键字创建“实例”。在我的回答中,我将使用person而不是Person.
  • setTimeout 是window对象的一个​​方法。self.setTimeout是不正确的。虽然在某些 javascript 实现self中是窗口,但在其他实现中不是,因此它是不可靠的。
  • this 总是当前的执行上下文,无论何时或如何发生。setTimeout 调用完全将函数从其正常的对象上下文中取出——它在那个时候只是一个函数,所以在执行时它没有预期的“this”对象。

我可以看到一些解决此问题的方法。

  1. 使用bind来“创建一个新函数,在调用该函数时,将其this关键字设置为提供的值。”:

    window.setTimeout(this.speak.bind(this), 1000);
    
  2. this将传递给 setTimeout 的函数包装在一个动态设置的匿名函数中:

    window.setTimeout(function(thisobj) {
       return function() {
          thisobj.speak.call(thisobj);
       }
    }(this), 1000);
    

    我们正在使用一个函数在thisobj参数上创建一个闭包,该闭包恰好是使用this 调用 setTimeout 时的当前 this 对象调用的。然后,该函数本身返回一个函数供 setTimeout 调用,并且该函数使用call(或者您可以使用apply)设置this对象以调用speak. 我们在这里所做的只是复制bind没有参数支持的功能——所以使用 bind 代替,除非你需要完整的跨浏览器和旧浏览器支持(在这种情况下你可以这样做,或者你可以加糖你的 javascript 所以绑定有效,代码位于我的答案底部)。但我想让你看看幕后发生了什么。

  3. 显式访问您的人员对象的成员:

    speak: function () {
        console.log (person.speech); // instead of this.speech
    }
    
  4. 将 Person 更改为对象构造函数,可用于创建新的“person”实例:

    function Person() {
        var me = this;
        this.speech = null;
        this.tryToSpeak = function () {
            me.speech = "hello";
            window.setTimeout(me.speak, 1000);
        };
        this.speak = function () {
            console.log(me.speech);
        };
    }
    
    var person = new Person();
    person.tryToSpeak();
    

    this通过在构造函数中捕获作为私有变量的副本me,您可以稍后在其他方法中使用它。现在this捕获的内容是有意义的,因为当您运行时,new Person()会有一个执行上下文(与以您的方式简单地声明一个对象不同,执行上下文是window)。这种方法的一个很大的缺点是对象的每个实例都有自己的函数副本,因为它们不能成为对象原型的一部分(为了访问私有变量me);

我相信还有其他方法可以处理它。你没有解释为什么你首先需要person成为一个对象,所以我不知道实现目标的最佳模式。您可能不需要多个人员对象,但您可能需要。对正在进行的上下文和目的进行更广泛的理解将有助于我更好地指导您。

注意:要“填充”没有的 javascript 实现bind,因此您可以更轻松地在函数上使用绑定,您可以这样做(注意与本机实现存在一些差异,请参阅与上面相同的绑定链接以获取详细信息):

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}
于 2012-11-26T18:54:03.917 回答
1

这是不可避免的。当你像这样传递函数时,它们会丢失它们的执行上下文。

解决方案是捕获“this”变量:

var self = this;
setTimeout(function() { self.speak(); }, 1000);
于 2012-11-26T18:23:33.487 回答
0

ErikE 的第四个建议的一个稍微不同的版本,它做同样的工作,但我认为更简单的代码是

function Person() {
    this.speech = null;
}
Person.prototype.tryToSpeak = function () {
    this.speech = "hello";
    var person = this;
    window.setTimeout(function() {person.speak();}, 1000);
};
Person.prototype.speak = function () {
    console.log(this.speech);
};


var person = new Person();
person.tryToSpeak();

正如 Erik 所说,您是否甚至需要多个Person对象都不是很清楚,但如果您需要,这种技术可能是最简单的。


更新(基于 ErikE 的评论):这个版本为 , 添加了一个名称Person,并将speechin 作为参数传递,以明确谁在说什么。基本结构保持不变。

function Person(name) {
    this.name = name;
}
Person.prototype.tryToSpeak = function (speech) {
    var person = this;
    window.setTimeout(function() {person.speak(speech);}, 1000);
};
Person.prototype.speak = function (speech) {
    console.log(this.name + " says, '" + speech + "'.");
};

var lincoln = new Person("Abraham Lincoln");
var churchill = new Person("Winston Churchill");
lincoln.tryToSpeak("Four score and seven years ago...");
churchill.tryToSpeak("Never, never, never give up.");

要点是person变量 intryToSpeak是存储在闭包中的局部变量,而不是对某个单例的引用。

于 2012-11-26T20:27:17.650 回答
-2

我不知道你为什么有缺点。这是典型的范围/参考错误。想一想,这会是什么?答案:坚果。

因此,您可以直接引用该对象,如下所示:

 // prints out Hello
 console.log (Person.speach);

或者将其作为属性传递给说话,就像这样:

self.setTimeout (this.speak(this), 1000);

Jsfiddle 两种情况:

http://jsfiddle.net/eukQH/

于 2012-11-26T18:28:29.717 回答