:我们有时决定不现在执行函数,而是等一段时间

  原文链接:,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(&#39;Hello&#39;)", 1000);</pre>

  但是使用字符串并不推荐,用函数吧:

  <pre class="prettyprint linenums prettyprinted" style="">setTimeout(() => alert(&#39;Hello&#39;), 1000);</pre>

  注:我们只传递函数,不执行

  js计时器设置延时弹窗_js实现计时增加_js 弹窗特效

  许多开发者经常犯的错误是在函数后面使用 () 调用它,这不需要!

  <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 中,则会阻止计时器计时。

  js计时器设置延时弹窗_js实现计时增加_js 弹窗特效

  如果你执行上面的代码,不关闭 alert 框的时候,在 FireFox/IE 中,下一个 alert 会立即弹出(距离上次调用已经过去两秒了),在 Chrome/Opera/Safari 中,得等个 2 秒后,才会弹出(在 alert 期间不会继续去计时)。

  递归调用 setTimeout

  有两种方法可以定期运行函数。

  一种是 setInterval,还有一个递归调用 setTimeout,像这样:

  <pre class="prettyprint linenums prettyprinted" style="">`// 而是用`let timerId = setTimeout(function tick() { alert(&#39;tick&#39;); 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):

  调度:setTimeout 和 setInterval - 图1

  注意到了吗?

  使用 setInterval 调用 func 时,每次函数之间的延迟比 100 毫秒要小!

  这很正常,因为时间间隔被中间的函数执行时间“消费了”。

  还有种可能,就是 func 函数的执行时间可能比 100 毫秒还要长呢。

  在这种情况下,引擎等待 func 完成,然后检查调度程序,如果时间到了,就立即运行它。

  在边缘情况下,如果函数的执行时间总是比延迟时间长,那么调用就会在没有暂停的情况下发生。

  这是递归 setTimeout 的过程:

  调度:setTimeout 和 setInterval - 图2

  递归 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

文章由官网发布,如若转载,请注明出处:https://www.veimoz.com/1867
0 评论
405

发表评论

!