使用setInterval和对象的Javascript奇怪行为

时间:2012-11-26 18:21:20

标签: javascript object settimeout

尝试在对象中使用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。这到底是怎么回事?这是不可避免的行为吗?

5 个答案:

答案 0 :(得分:3)

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

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

详细了解javascript this keyword

答案 1 :(得分:2)

首先注意一些事项:

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

我可以看到一些方法来解决这个问题。

  1. 使用bind“创建一个新功能,在调用时,将关键字设置为提供的值。”:

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

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

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

  3. 明确访问person对象的成员:

    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); < / p>

  5. 我确信还有其他方法可以处理它。您没有解释为什么首先需要person成为对象,所以我不知道实现目标的最佳模式。您可能不会需要多人对象,但您可能会再次。对背景和目的的更广泛理解将有助于我更好地指导你。

    注意:要“填充”没有bind的javascript实现,这样你就可以更容易地对函数使用bind了,你可以这样做(注意与本机实现有一些不同,请看同样的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;
      };
    }
    

答案 2 :(得分:1)

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

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

var self = this;
setTimeout(function() { self.speak(); }, 1000);

答案 3 :(得分:0)

有点不同版本的ErikE's fourth suggestion执行相同的工作,但我认为代码更简单

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添加了一个名称,并将speech作为参数传递,以明确指出谁是说什么。基本结构保持不变。

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中的tryToSpeak变量是存储在闭包中的局部变量,而不是对某些单例的引用。

答案 4 :(得分:-2)

我不知道你为什么会有弊端。这是典型的范围/参考错误。想想它一分钟,可能是什么 这个在说什么?答案:坚果。

所以你可以直接引用这个对象,如下所示:

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

或者将它作为一个属性来传递,就像那样:

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

Jsfiddle两个案例:

http://jsfiddle.net/eukQH/