这篇翻译不完整。请帮忙从英语翻译这篇文章

 WindowOrWorkerGlobalScope 混合的 setTimeout()方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。

语法

var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);
var timeoutID = scope.setTimeout(function[, delay]); 
var timeoutID = scope.setTimeout(code[, delay]);

参数

function
function 是你想要在delay毫秒之后执行的函数
code
这是一个替代语法,你可以使用字符串代替function ,在delay毫秒之后执行字符串 (使用该语法是不推荐的, 原因和使用 eval()一样,有安全风险)。
delay 可选
延迟的毫秒数 (一秒等于1000毫秒),函数的调用会在该延迟之后发生。如果省略该参数,delay取默认值0。实际的延迟时间可能会比 delay 值长,原因请查看Reasons for delays longer than specified
param1, ..., paramN 可选
附加参数,一旦定时器到期,它们会作为参数传递给function 或 执行字符串(setTimeout参数中的code)

备注:需要注意的是,IE9 及更早的 IE 浏览器不支持第一种语法中向延迟函数传递额外参数的功能。如果你想要在IE中达到同样的功能,你必须使用一种兼容代码 (查看 callback arguments 一段).

返回值

返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时。

需要注意的是setTimeout()和setInterval()共用一个编号池,技术上,clearTimeout()和 clearInterval() 可以互换。但是,为了避免混淆,不要混用取消定时函数。

在同一个对象上(一个window或者worker),setTimeout()或者setInterval()返回的定时器编号不会重复。但是不同的对象使用独立的编号池。

例子

下文的例子在网页中设置了两个简单的按钮,以触发 setTimeout() 和 clearTimeout() 方法:按下第一个按钮会在 2s 后显示一个警告对话框,并将此次 setTimeout 的延时 ID 保存起来。按下第二个按钮可以取消这次延时调用行为。

HTML 内容

<p>Live Example</p>
<button onclick="delayedAlert();">Show an alert box after two seconds</button>
<p></p>
<button onclick="clearAlert();">Cancel alert before it happens</button>
 
 
 
 

JavaScript 内容

var timeoutID;

function delayedAlert() {
  timeoutID = window.setTimeout(slowAlert, 2000);
}

function slowAlert() {
  alert('That was really slow!');
}

function clearAlert() {
  window.clearTimeout(timeoutID);
}
 
 
 
 
 
 
 
 
 
 
 
 
 

结果

也可参考 clearTimeout() 示例.

兼容旧环境(polyfill)

如果你需要向你的回调函数内传递一个参数, 而且还需要兼容IE9及以前的版本, 由于IE不支持传递额外的参数 (setTimeout() 或者 setInterval()都不可以) ,但你可以引入下面的兼容代码.该代码能让IE也支持符合HTML5标准的定时器函数.

/*\
|*|
|*|  Polyfill which enables the passage of arbitrary arguments to the
|*|  callback functions of JavaScript timers (HTML5 standard syntax).
|*|
|*|  https://developer.mozilla.org/en-US/docs/DOM/window.setInterval
|*|
|*|  Syntax:
|*|  var timeoutID = window.setTimeout(func, delay[, param1, param2, ...]);
|*|  var timeoutID = window.setTimeout(code, delay);
|*|  var intervalID = window.setInterval(func, delay[, param1, param2, ...]);
|*|  var intervalID = window.setInterval(code, delay);
|*|
\*/

(function() {
  setTimeout(function(arg1) {
    if (arg1 === 'test') {
      // feature test is passed, no need for polyfill
      return;
    }
    var __nativeST__ = window.setTimeout;
    window.setTimeout = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
      var aArgs = Array.prototype.slice.call(arguments, 2);
      return __nativeST__(vCallback instanceof Function ? function() {
        vCallback.apply(null, aArgs);
      } : vCallback, nDelay);
    };
  }, 0, 'test');

  var interval = setInterval(function(arg1) {
    clearInterval(interval);
    if (arg1 === 'test') {
      // feature test is passed, no need for polyfill
      return;
    }
    var __nativeSI__ = window.setInterval;
    window.setInterval = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
      var aArgs = Array.prototype.slice.call(arguments, 2);
      return __nativeSI__(vCallback instanceof Function ? function() {
        vCallback.apply(null, aArgs);
      } : vCallback, nDelay);
    };
  }, 0, 'test');
}())

IE Only Fix

如果你需要单独的针对IE9及之前浏览器的 hack 写法,你可以使用 JavaScript 条件注释:

/*@cc_on
  // conditional IE < 9 only fix
  @if (@_jscript_version <= 9)
  (function(f){
     window.setTimeout = f(window.setTimeout);
     window.setInterval = f(window.setInterval);
  })(function(f){return function(c,t){var a=[].slice.call(arguments,2);return f(function(){c instanceof Function?c.apply(this,a):eval(c)},t)}});
  @end
@*/
 
 
 
 
 
 
 
 
 

或者使用更加清晰的 IE HTML 条件注释:

<!--[if lte IE 9]><script>
(function(f){
window.setTimeout=f(window.setTimeout);
window.setInterval=f(window.setInterval);
})(function(f){return function(c,t){
var a=[].slice.call(arguments,2);return f(function(){c instanceof Function?c.apply(this,a):eval(c)},t)}
});
</script><![endif]-->
 
 
 
 
 
 
 
 

解决方法

另一种方法是使用匿名函数包裹你的回调函数,这种方式要消耗更多资源:

var intervalID = setTimeout(function() { myFunc('one', 'two', 'three'); }, 1000);
 

上面那个例子也可以用arrow function:

var intervalID = setTimeout(() => { myFunc('one', 'two', 'three'); }, 1000);

此外,也可使用 function's bind

setTimeout(function(arg1){}.bind(undefined, 10), 1000);

关于"this"的问题

当你向 setTimeout() (或者其他函数)传递一个函数时,该函数中的this会指向一个错误的值.这个问题在 JavaScript reference 中进行了详细解释.

解释

setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined,这和所期望的this的值是不一样的。

备注:在严格模式下,setTimeout( )的回调函数里面的this仍然默认指向window对象, 并不是undefined

查看下面的例子:

let 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"

上面这段代码正常工作,用myArray调用,在函数内,this[sProperty]等于 myArray[sProperty]。然后,下面这个例子:

setTimeout(myArray.myMethod, 1000); // prints "[object Window]" after 1 second
setTimeout(myArray.myMethod, 1500, "1"); // prints "undefined" after 1.5 seconds

myArray.myMethod函数传递给 setTimeout,到了定时时间,this没有指向,默认指向window对象。并且没有方法把 thisArg 传递给setTimeout,正如Array方法的forEach,reduce等。下面的例子表示使用call方法也没用。

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

可能的解决方案

一个通用的方法是用包装函数:

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

箭头函数也可以:

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

另一个解决 "this" 问题的方法是使用两个非原生的 setTimeout()setInterval() 全局函数代替原生的。该非原生的函数通过使用Function.prototype.call 方法激活了正确的作用域。下面的代码显示了应该如何替换:

// Enable the passage of the 'this' object through the 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);
};
备注: 这两个替换也让 IE支持了符合 HTML5 标准的定时器函数。所以也能作为一个 polyfills。查看 Callback arguments 一段.

新特性检测:

let 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 seconds
setTimeout.call(myArray, myArray.myMethod, 2500, 2); // prints "two" after 2,5 seconds

针对这个问题并没有原生的解决方案。

注:JavaScript 1.8.5 引入了 Function.prototype.bind() 方法,该方法允许显式地指定函数调用时 this 所指向的值 。该方法可以帮助你解决 this 指向不确定的问题。

使用bind的例子:

let 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 function
myBoundMethod(1); // prints "one"
setTimeout(myBoundMethod, 1000); // still prints "zero,one,two" after 1 second because of the binding
setTimeout(myBoundMethod, 1500, "1"); // prints "one" after 1.5 seconds

备注

你可以使用 window.clearTimeout()来取消延迟操作。如果你希望你的代码被重复的调用 (比如每 N 毫秒一次),考虑使用 window.setInterval()

记住这一点:只有当调用setTimeout()的线程停止后,函数或代码段才能继续执行。

传递字符串字面量

setTimeout()传递一个字符串而不是函数会遭受到与使用eval一样的风险.

// 推荐
window.setTimeout(function() {
    alert("Hello World!");
}, 500);

// 不推荐
window.setTimeout("alert(\"Hello World!\");", 500);

字符串会在全局作用域内被解释执行,所以当setTimeout()函数执行完毕后,字符串中的变量不可用.

 

实际延时比设定值更久的原因:最小延迟时间

有很多因素会导致setTimeout的回调函数执行比设定的预期值更久,本节将讨论最常见的原因。

最小延时 >=4ms

在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的setInterval的回调函数阻塞导致的。例如:

function cb() { f(); setTimeout(cb, 0); }
setTimeout(cb, 0);

 

setInterval(f, 0);

在Chrome 和 Firefox中, 定时器的第5次调用被阻塞了;在Safari是在第6次;Edge是在第3次。Gecko 从这个版本 version 56开始对 setInterval() 开始采用这样的机制(setTimeout()已经实现,具体请参考以下内容)。

一直以来,不同浏览器中出现这种最小延迟的情况有所不同(例如Firefox) - 从其他地方调用了setInterval( ),或者在嵌套函数调用setTimeout( ) 时(嵌套级别达到特定深度时),都会出现超时延迟。

如果想在浏览器中实现0ms延时的定时器,你可以参考这里所说的{domxref("window.postMessage()")}}

Note: 最小延时, DOM_MIN_TIMEOUT_VALUE, 是4ms  (但在Firefox中通常是是存储在 dom.min_timeout_value 这个变量中), DOM_CLAMP_TIMEOUT_NESTING_LEVEL 的第5层.

Note: 4 ms 是在  HTML5 spec  中精确的,并且在2010年及以后的跨浏览器中保持了一致,这个数值比 (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2)规定的嵌套函数的最小延时10ms更为精确。

未被激活的tabs的定时最小延迟>=1000ms

为了优化后台tab的加载损耗(以及降低耗电量),在未被激活的tab中定时器的最小延时限制为1S(1000ms)。

Firefox 从version 5 (see bug 633421开始采取这种机制,1000ms的间隔值可以通过 dom.min_background_timeout_value 改变。Chrome 从 version 11 (crbug.com/66078)开始采用。

Android 版的Firefox对未被激活的后台tabs的使用了15min的最小延迟间隔时间 ,并且这些tabs也能完全不被加载。

当 Web Audio API AudioContext 正在被用来播放音频的时候,Firefox 50不会再限制后台tabs的加载。 后续的Firefox 51 版本修正了这个问题,即使在没有音频播放的情况下,也不再限制后台tabs的加载。这个解决了一些软件应用在后台tabs中播放基于文本的音频( note-based) 时,无法去同步音频和画面的问题。

 

 

追踪型脚本的最小延时限制

从Firefox 55版本开始,追踪型脚本(例如 谷歌分析,或者其他的一些被Firefox 的 TP lists 识别为追踪型脚本的 外链URL脚本)是重点限制加载的对象。在当前正在使用的页面中,这个节流限制的延时依然是4ms。但是在后台tabs中,这个最小延时限制是10000ms(10s),这个限制会在文档第一次加载后的30s后生效。

控制这些行为的属性包含以下这些:

  • dom.min_tracking_timeout_value: 4
  • dom.min_tracking_background_timeout_value: 10000
  • dom.timeout.tracking_throttling_delay: 30000

超时延迟

除了"最小延时"之外,定时器仍然有可能因为当前页面(或者操作系统/浏览器本身)被其他任务占用导致延时。 需要被强调是, 直到调用 setTimeout()的主线程执行完其他任务之后,回调函数和代码段才能被执行。例如:

function foo() {
  console.log('foo has been called');
}
setTimeout(foo, 0);
console.log('After setTimeout');

会在控制台输出:

After setTimeout
foo has been called

出现这个结果的原因是,尽管setTimeout 以0ms的延迟来调用函数,但这个任务已经被放入了队列中并且等待下一次执行;并不是立即执行;队列中的等待函数被调用之前,当前代码必须全部运行完毕,因此这里运行结果并非预想的那样。

 

最大延时值

浏览器包括 IE, , Chrome, Safari, Firefox 以32个bit字节存储整数。这就会导致如果一个整数大于 2147483647 (大约24.8 天)时就会溢出,导致定时器将会被立即执行。

浏览器兼容性

 兼容性表 

详情 谷歌浏览器 火狐浏览器 (Gecko) IE浏览器 Opera浏览器 Safari
Basic support 1.0 1.0 (1.7 or earlier) 4.0 4.0 1.0
Supports parameters for callback*1 (Yes) (Yes) 10.0 (Yes) ?
详情 Android Chrome for Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Basic support 1.0 1.0 1.0 (1) 6.0 6.0 1.0
Supports parameters for callback*1 ? ? ? ? ? ?

*1 Whether it supports the optional parameters when in its first form or not.

规范

Part of DOM level 0, as specified in HTML5.

相关链接:

文档标签和贡献者

最后编辑者: LilyWakana,