WindowOrWorkerGlobalScope接口方法:setTimeout()

2018-03-29 10:28 更新

setTimeout()方法

WindowOrWorkerGlobalScope mixin 的 setTimeout() 方法  (以及 window.setTimeout 的后继)用于设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。

setTimeout()方法语法

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

setTimeout()方法参数

function
在计时器到期后执行 function 
code
另一种语法允许您包含一个字符串,而不是一个函数,该函数在计时器到期时编译并执行。不建议出于使用eval()安全风险的相同原因而使用此语法。
delay 可选的
时间(以毫秒(千分之一秒)为单位),计时器应该在指定的功能或代码执行之前等待。如果省略此参数,则使用值0,意味着尽快执行“immediately”或更准确地执行。请注意,在任何一种情况下,实际延迟可能比预期的要长。
param1, ..., paramN 可选的
额外的参数一旦定时器到期就传递给由 function 或 code 指定的函数。

注意:在 Internet Explorer 9 及更低版本中,将第一种语法的附加参数传递给函数不起作用。如果您想在该浏览器上启用此功能,则必须使用填充(polyfill),请参考本文的 Polyfill 部分。

setTimeout()方法返回值

setTimeout() 方法返回的 timeoutID 是一个正整数值,用于标识由该 setTimeout() 调用创建的定时器;这个值可以传递 clearTimeout() 以取消超时。

了解 setTimeout() 和 setInterval() 共享相同的 ID 池可能会有所帮助,并且 clearTimeout() 和 clearInterval() 在技术上可以互换使用。但是,为了清晰起见,您应该尽量始终与它们匹配以避免在维护代码时出现混淆。

保证在同一对象(window 或 worker)上对 setTimeout () 或 setInterval () 的后续调用将永远不会重用超时 ID。但是,不同的对象使用单独的 ID 池。

setTimeout()方法示例

以下示例在网页中设置两个简单按钮并将它们挂接到该例程 setTimeout() 和 clearTimeout()例程。按下第一个按钮将设置一个超时,在两秒钟后调用一个警报对话框,并存储要使用的 clearTimeout() 超时 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);
}

Polyfill(填充工具)

如果您需要将一个或多个参数传递给您的回调函数,但需要它在不支持使用 setTimeout() 或者 setInterval() (例如 Internet Explorer 9 及以下版本)发送附加参数的浏览器中工作),则可以包括此填充代码,其中启用 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只修复

如果您希望对其他移动浏览器或桌面浏览器(包括 IE 9 及以下版本)进行完全不引人注意的攻击,则可以使用 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]-->

解决方法

另一种可能是使用 anonymous 函数来调用你的回调函数,但是这个解决方案有点消耗,例如:

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

上面的例子也可以在 arrow 函数的帮助下编写:

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

另一种可能性是使用函数的 bind,例如:

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

“this”问题

当你传递一个方法给 setTimeout()(或任何其他函数)时,它会被一个 this 值调用,这可能与你的期望不同的。

说明

由 setTimeout() 执行的代码是从调用 setTimeout 的函数分开的执行上下文中调用。通常 this 为被调用函数设置关键字的规则适用,如果您未在调用中设置 this 或使用 bind,在非严格模式它将默认为 global(或 window)对象,或者在严格模式下未定义。它将与调用 setTimeout 的函数的 this 值不相同。

注意:即使在严格模式下,回调的默认 this 值 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 second
setTimeout(myArray.myMethod, 1500, '1'); // prints "undefined" after 1.5 seconds

该 myArray.myMethod 函数被传递给 setTimeout它,然后当它被调用时,this 没有被设置,所以它默认为该 window 对象。也没有选择传递 thisArg 给setTimeout,因为在 Array 方法中有像 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 seconds
setTimeout(function(){myArray.myMethod('1')}, 2500); // prints "one" after 2.5 seconds

arrow 函数也是一种可能的选择:

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() 全局函数替换为允许传递 this 对象并在使用 Function.prototype.call 的回调中设置它,例如:

// 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);
};
注意

:这两个替代品也将启用 HTML5 标准的任意参数传递给 IE 中定时器的回调函数。所以他们也可以用作 polyfills。

新功能测试:

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() 示例:

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

setTimeout()方法笔记

使用 clearTimeout () 取消超时。要重复调用函数(例如,每N毫秒),请考虑使用setInterval()。

传递字符串文字

传递一个字符串而不是一个函数给 setTimeout(),承受与使用 eval 相同的危险。

// Recommended
window.setTimeout(function() {
    alert('Hello World!');
}, 500);

// Not recommended
window.setTimeout("alert('Hello World!');", 500);

传递给 setTimeout 的字符串在全局上下文中被计算,因此在将字符串计算为代码时,调用 setTimeout () 的上下文中的本地符号将不可用。

延误时间超过指定时间的原因

有多种原因可能会导致超时时间超出预期。本节介绍最常见的原因。

超时限制为 >= 4ms

在目前使用的浏览器中,由于回调嵌套(嵌套层次至少达到一定深度),或者在一定数量的连续时间间隔后触发连续调用时,setTimeout()/setInterval() 调用每4ms 至少被节流一次。例如:

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

在 Chrome 和 Firefox 中,第五个连续回调调用被夹紧;Safari 在第 6 次通话中夹紧;在 Edge 中它是第三个。Gecko 在版本56中开始这样对待 setInterval()。

从历史上看,一些浏览器实施这种限制行为有点不同(例如 Firefox) - setInterval() 从任何地方进行调用,或者在 setTimeout() 嵌套级别至少达到特定深度时调用嵌套。

要在现代浏览器中实现 0 ms 超时,可以使用 window.postMessage(),如下所述。

注意:所述的最小延迟,DOM_MIN_TIMEOUT_VALUE 是 4 毫秒(存储在Firefox 的首选项中: dom.min_timeout_value),DOM_CLAMP_TIMEOUT_NESTING_LEVEL 为 5。 

注意:4 ms 由 HTML5 规范指定,并且在 2010 年及以后发布的浏览器中保持一致。在(Firefox 5.0/Thunderbird 5.0/SeaMonkey 2.2)之前,嵌套超时的最小超时值为10毫秒。

非活动选项卡中的超时限制为 >= 1000毫秒

为了减少后台选项卡中的负载(以及相关的电池使用),在非活动选项卡中,超时将被限制为每秒不超过一次(1000毫秒)触发。

Firefox 从版本5开始实现这种行为(可以通过 dom.min_background_timeout_value 首选项调整 1000ms 常量)。Chrome 从版本11开始实施此行为(crbug.com/66078)。

Firefox 为 Android 使用15分钟的超时值作为 Firefox 14 中错误 736602 的后台选项卡,并且后台选项卡也可以完全卸载。

如果 Web Audio API AudioContext 正在播放声音,则 Firefox 50 不再限制后台选项卡。Firefox 51 进一步修正了这个问题,即使没有声音播放,如果标签中的 AudioContext 存在,后台选项卡也不再被限制。这些解决了使用基于笔记的音乐的应用程序在选项卡位于后台时无法正确计时或同步音乐的许多问题。

限制跟踪超时脚本

自 Firefox 55 以来,跟踪脚本(例如 Google Analytics,Firefox 通过其 TP 列表识别为跟踪脚本的任何脚本 URL )都受到进一步限制。在前台运行时,节流最小延迟仍为 4ms。但是,在后台选项卡中,限制最小延迟时间为10000毫秒或10秒,这将在文档第一次加载后30秒生效。

控制这种行为的前提是:

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

延迟超时

除了“clamping”之外,当页面(或操作系统/浏览器本身)忙于其他任务时,超时还可以稍后触发。需要注意的一个重要情况是,直到调用 setTimeout() 的线程已终止才能执行该函数或代码片段。例如:

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

将写入控制台:

After setTimeout
foo has been called

这是因为即使 setTimeout 被称为延迟零,它被放置在一个队列中,并计划在下一个机会运行;不是马上当前执行的代码必须在执行队列上的函数之前完成,因此结果的执行顺序可能不如预期。

最大延迟值

包括 Internet Explorer,Chrome,Safari 和 Firefox 在内的浏览器将延迟存储为内部 32 位有符号整数。当使用大于 2147483647 的延迟(大约24.8天)时,会导致整数溢出,导致立即执行超时。

规范

规范 状态 评论
HTML Living Standard 
在该规范中定义'WindowOrWorkerGlobalScope.setTimeout()'。
Living Standard
方法转移到最新规范中的WindowOrWorkerGlobalScope mixin。
HTML Living Standard 
规范 中'WindowTimers.setTimeout()'的定义。             
Living Standard
初始定义(DOM Level 0)

浏览器兼容性

我们正在将兼容性数据转换为机器可读的JSON格式。

  • 电脑端
特征 Chrome
Edge
Firefox(Gecko)
Internet Explorer
Opera
Safari
基本支持 支持:1.0 支持 支持:1.0(1.7或更早)、52[2] 支持:4 支持:4 支持:1.0
支持回调参数[1] 支持 支持 支持 支持:10.0 支持 支持
限制跟踪超时脚本 支持:55
  • 移动端

特征AndroidChrome for AndroidEdgeFirefox Mobile (Gecko)IE MobileOpera MobileSafari Mobile
基本支持支持:1.0支持:1.0支持支持:1.0、52.0[2]支持:6.0支持:6.0支持:1.0
支持回调函数[1]???????
限制跟踪超时脚本???支持:55.0 ???

注释:

[1]它是否支持第一种形式的可选参数。

[2] setTimeout() 现在在 WindowOrWorkerGlobalScope mixin 上定义。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号