javascript setInterval和setTimeout的this问题

来源:互联网 发布:王诺诺 知乎 编辑:程序博客网 时间:2024/06/06 08:15

The "this" problem

如果你通过setTimeout函数(或其他函数,或其他情况),调用的时候this的值可能并不能像你期待的那样。这种情况已经在Javascript reference里面详细的介绍过了。


    说明    

setTimeout()函数里执行的代码是与调用setTimeout函数的执行上下文分开的。正常的方法是为被调用的函数设置this的关键字,而如果你没有为该函数设置call或者bind的话,在不严格模式下会默认this为全局变量(也可以是window)。所以调用setTimeout的时候的this值可能不同。

Note:即使在严格模式下,setTimeout回调的默认值仍然是window对象,而不是undefined。 

来看看下面的例子:

myArray = ['zero', 'one', 'two'];myArray.myMethod = function (sProperty) {    alert(arguments.length > 0 ? this[sProperty] : this);};myArray.myMethod(); // prints "zero,one,two"myArray.myMethod(1); // prints "one"

以上的结果是因为当myMethod被调用的时候,它的this值被设置为myArray,所以在该函数里,this[sProperty]等于myArray[sProperty]。

然而,再看看下面:

setTimeout(myArray.myMethod, 1000); // prints "[object Window]" after 1 secondsetTimeout(myArray.myMethod, 1500, '1'); // prints "undefined" after 1.5 seconds
myArray.myMethod函数通过setTimeout来调用,这样当它被调用的时候,它的this值就没有设置,所以是默认值windwo对象。还有,如果没有选择将this参数传递给setTimeout,就像在forEach,reduce等数组函数中一样,如下所示,使用call来设置this变量是不起作用。

setTimeout.call(myArray, myArray.myMethod, 2000); // error: "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO: Illegal operation on WrappedNative prototype object"setTimeout.call(myArray, myArray.myMethod, 2500, 2); // same error


    解决方法    

一般的解决这种问题的方法是包装函数,将this设置为需要的值:

setTimeout(function(){myArray.myMethod()}, 2000); // prints "zero,one,two" after 2 secondssetTimeout(function(){myArray.myMethod('1')}, 2500); // prints "one" after 2.5 seconds
箭头函数也是一个可能的选择:

setTimeout(() => {myArray.myMethod()}, 2000); // prints "zero,one,two" after 2 secondssetTimeout(() => {myArray.myMethod('1')}, 2500); // prints "one" after 2.5 seconds

另一种解决"this"问题的方法是将原本的setTimeout()和setInterval()替换为允许传递this对象和通过使用Function.prototype.call设置this在回调函数中,例如:

// Enable setting 'this' in JavaScript timers var __nativeST__ = window.setTimeout,     __nativeSI__ = window.setInterval; window.setTimeout = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {  var oThis = this,       aArgs = Array.prototype.slice.call(arguments, 2);  return __nativeST__(vCallback instanceof Function ? function () {    vCallback.apply(oThis, aArgs);  } : vCallback, nDelay);}; window.setInterval = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {  var oThis = this,      aArgs = Array.prototype.slice.call(arguments, 2);  return __nativeSI__(vCallback instanceof Function ? function () {    vCallback.apply(oThis, aArgs);  } : vCallback, nDelay);};

Note:这两个替换方案也可以使HTML5标准的任意参数能通过IE的定时器的回调函数。所以它们也被用来作profills 。

有关profill,大家可以自行查阅。

替换setInterval和setTimeout后的新功能的测试:

myArray = ['zero', 'one', 'two'];myArray.myMethod = function (sProperty) {    alert(arguments.length > 0 ? this[sProperty] : this);};setTimeout(alert, 1500, 'Hello world!'); // the standard use of setTimeout and setInterval is preserved, but...setTimeout.call(myArray, myArray.myMethod, 2000); // prints "zero,one,two" after 2 secondssetTimeout.call(myArray, myArray.myMethod, 2500, 2); // prints "two" after 2.5 seconds
Note:Javascript 1.8.5 引入了Function.prototype.bind()函数来为给定函数设置this值。这样可以避免使用包装函数为设置回调中的值

使用bind()的例子:

myArray = ['zero', 'one', 'two'];myBoundMethod = (function (sProperty) {    console.log(arguments.length > 0 ? this[sProperty] : this);}).bind(myArray);myBoundMethod(); // prints "zero,one,two" because 'this' is bound to myArray in the functionmyBoundMethod(1); // prints "one"setTimeout(myBoundMethod, 1000); // still prints "zero,one,two" after 1 second because of the bindingsetTimeout(myBoundMethod, 1500, "1"); // prints "one" after 1.5 seconds
翻译资料:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout中的 The "this" problem 选段。