:我们有时决定不现在执行函数,而是等一段时间
原文链接:,translate with ❤️ by zhangbao.
我们有时决定不现在执行函数,而是等一段时间。这称为“调度调用”。
有两个方法供使用:
setTimeout
语法:
<pre class="prettyprint linenums prettyprinted" style="">let timerId = setTimeout(func|code, delay[, arg1, arg2...])
</pre>
参数:
func|code``
要执行的函数和字符串类型的代码。通常是一个函数。因为历史原因,字符串代码也可以被传递,但是并不推荐。
delay
执行前的延迟时间,以毫秒计(1000ms = 1秒)。
arg1,arg2...
传递给函数的参数(IE9- 不支持)。
例如,下面代码在 1 秒后调用 sayHi():
<pre class="prettyprint linenums prettyprinted" style="">function sayHi() {
` alert('Hello');}
`setTimeout(sayHi, 1000);
</pre>
携带参数:
<pre class="prettyprint linenums prettyprinted" style="">function sayHi(phrase, who) {
` alert( phrase + ', ' + who );}
`setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John
</pre>
如果第一个参数是字符串,那么 JavaScript 就会用它创建函数。
就是说,下面也是可以的:
<pre class="prettyprint linenums prettyprinted" style="">setTimeout("alert('Hello')", 1000);
</pre>
但是使用字符串并不推荐,用函数吧:
<pre class="prettyprint linenums prettyprinted" style="">setTimeout(() => alert('Hello'), 1000);
</pre>
注:我们只传递函数,不执行
许多开发者经常犯的错误是在函数后面使用 () 调用它,这不需要!
<pre class="prettyprint linenums prettyprinted" style="">// 错误的!
`setTimeout(sayHi(), 1000);`</pre>
这不行,因为 setTimeoiut 期望的是一个函数引用。sayHi() 是执行函数,就是把函数执行之后的结果传递给 setTimeout。在我们这个例子里,sayHi() 返回的结果是 undefined(函数没有返回值),所以没有啥会进入调度。
使用 clearTimeout 取消定时器
调用 setTimeout 后,返回的是“定时器 ID”timerId,我们可以用它去取消定时器。
语法如下:
<pre class="prettyprint linenums prettyprinted" style="">let timerId = setTimeout(...);
`clearTimeout(timerId);`</pre>
在下面的代码中,我们调度函数,然后取消它(改变了注意)。结果,什么也没发生:
<pre class="prettyprint linenums prettyprinted" style="">let timerId = setTimeout(() => alert("never happens"), 1000);
`alert(timerId); // 定时器 id``clearTimeout(timerId);
alert(timerId); // 相同的 id 值(在取消之后没有变成 null)`</pre>
正如我们从警报输出中看到的,在浏览器中,计时器标识符是一个数字。在其他环境中,这可能是另一种情况。例如,Node.js 返回一个带有附加方法的计时器对象。
同样,这些方法没有通用规范,所以这没什么错。
对于浏览器来说,计时器是在 HTML5 标准的中描述的。
setInterval
setInterval 方法有着与 setTimeout 一样的语法:
<pre class="prettyprint linenums prettyprinted" style="">let timerId = setInterval(func|code, delay[, arg1, arg2...])
</pre>
所有的参数都是一个意思。但是不像 setTimeout,他执行函数可不是一次,而是在指定间隔去定时触发的。
为了阻止进一步调用,我们应该调用 clearInterval(timerId)。
下面的例子将每2秒显示一条消息。5秒后,输出停止:
<pre class="prettyprint linenums prettyprinted" style="">// 每 2 秒重复一次
`let timerId = setInterval(() => alert('tick'), 2000);``// 5 秒钟后停止
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);`</pre>
注:在 Chrome/Opera/Safari 中,弹出的模态框会阻止时间进行。
在 IE 和 FireFox 浏览器中,当显示 alert/confirm/prompt 弹框时,内部计时器会继续“计时”,但在 Chrome/Opera/Safair 中,则会阻止计时器计时。
如果你执行上面的代码,不关闭 alert 框的时候,在 FireFox/IE 中,下一个 alert 会立即弹出(距离上次调用已经过去两秒了),在 Chrome/Opera/Safari 中,得等个 2 秒后,才会弹出(在 alert 期间不会继续去计时)。
递归调用 setTimeout
有两种方法可以定期运行函数。
一种是 setInterval,还有一个递归调用 setTimeout,像这样:
<pre class="prettyprint linenums prettyprinted" style="">`
// 而是用
`let timerId = setTimeout(function tick() { alert('tick');
timerId = setTimeout(tick, 2000); // (*)`}, 2000);
</pre>
上面的 setTimeout 会在 (*) 处之后立即调用下一次调度。
递归调用 setTimeout 是一种比 setInterval 更加灵活的方式。通过这种方式,下一个调用可能会以不同的方式调度,这取决于当前的结果。
例如,我们需要编写一个服务,它每5秒钟向服务器发送一个请求,请求数据,但是如果服务器超载,它应该将间隔增加到 10、20、40 秒。
这是伪代码:
<pre class="prettyprint linenums prettyprinted" style="">let delay = 5000;
`let timerId = setTimeout(function request() {
...send request... if (request failed due to server overload) {`` // 下次调用时,增加间隔`` delay *= 2;`` }
timerId = setTimeout(request, delay);`
`}, delay);`</pre>
如果我们经常有一个非常耗费 CPU 的任务,那么我们就可以测量执行所花费的时间,并尽早计划下一次的调用。
递归调用 setTimeout 保证了执行之间的延迟,而 setInterval 不会。
让我们比较两个代码片段。第一个使用 setInterval:
<pre class="prettyprint linenums prettyprinted" style="">let i = 1;
`setInterval(function() { func(i);
}, 100);`</pre>
第二种方法使用递归 setTimeout:
<pre class="prettyprint linenums prettyprinted" style="">let i = 1;
`setTimeout(function run() { func(i);
setTimeout(run, 100);`}, 100);
</pre>
对 setInterval 来说,内部的调度器会每隔 100 毫秒就执行下 func(i):
注意到了吗?
使用 setInterval 调用 func 时,每次函数之间的延迟比 100 毫秒要小!
这很正常,因为时间间隔被中间的函数执行时间“消费了”。
还有种可能,就是 func 函数的执行时间可能比 100 毫秒还要长呢。
在这种情况下,引擎等待 func 完成,然后检查调度程序,如果时间到了,就立即运行它。
在边缘情况下,如果函数的执行时间总是比延迟时间长,那么调用就会在没有暂停的情况下发生。
这是递归 setTimeout 的过程:
递归 setTimeout 保证了固定的延迟(这里是100 ms)。
这是因为在前一个调用的末尾有一个新的调用。
注:垃圾收集
当一个函数在 setinterval/settimeout 中传递时,会创建一个内部引用,并保存在调度程序中,它可以避免函数即使在没有其他的引用的情况下,也不会被被垃圾收集器清除。
<pre class="prettyprint linenums prettyprinted" style="">// 函数保留在内存里知道调度程序调用它
`setTimeout(function() {...}, 100);`</pre>
于 setInterval 来说,函数在内存中停留,直到 clearInterval 被调用。
有一个副作用。函数引用外部词汇环境,因此,当它活着时,外部变量也会存在。它们可能比函数本身占用更多的内存。所以当我们不再需要调度函数时,最好取消它,即使它很小。
setTimeout(…, 0)
有一个特殊用例:setTimeout(func, 0)。
这将尽快安排 func 的执行。但是调度器只有在当前代码完成之后才会调用它。
以这个函数被调度在当前代码之后运行。换句话说,就是异步。
例如,这个输出“Hello”,然后立即“world”:
<pre class="prettyprint linenums prettyprinted" style="">setTimeout(() => alert("World"), 0);
``alert("Hello");
</pre>
第一行“在0ms之后将调用放入日历中”。但是,在当前代码完成之后,调度程序只会“检查日历”,所以“Hello”先打印出来,之后打印“world”。
分离强 CPU 消耗任务
这里有一个技巧,使用 setTimeout 来分离消耗 CPU 的任务。
例如,语法高亮脚本(用于在页面上着色代码示例)是非常耗费 CPU 的。为了高亮代码,它执行分析、创建许多有色元素,并将它们添加到文档中——对于大文本来说就比较耗费资源。它甚至可能会导致浏览器“挂起”,这是不可接受的。
因此我们可以将长文本分离成一个个片段。使用 setTimeout(…, 0) 先执行前 100 行,然后在执行后续的 100 行代码等等。
为了清晰起见,我们来看一个更简单的例子。我们有一个函数从 1 计数到 1000000000。
如果你执行这个脚本,CPU 就会挂起。在服务器端 JS 上会表现的很明显,如果实在浏览器中执行,尝试点击页面中的按钮不会有响应——整个 JavaScript 脚本都会暂停,在它完成之前,没有其他的操作可以进行。
<p><pre class="prettyprint linenums prettyprinted" style="">let i = 0;
`let start = Date.now();
`function count() {
`` // do a heavy job
for (let j = 0; j
发表评论
热门文章
Spimes主题专为博客、自媒体、资讯类的网站设计....
一款个人简历主题,可以简单搭建一下,具体也比较简单....
仿制主题,Typecho博客主题,昼夜双版设计,可....
用于作品展示、资源下载,行业垂直性网站、个人博客,....
尘集杂货铺和官网1t5-cn
11月11日
[已回复]
希望主题和播放器能支持SQLite数据库,AI能多个讯飞星火