HTML5 规范规定最小延迟时间不能小于 4ms,即 x 如果小于 4,会被当做 4 来处理。 不过不同浏览器的实现不一样,比如,Chrome 可以设置 1ms,IE11/Edge 是 4ms。
setTimeout 注册的函数 fn 会交给浏览器的定时器模块来管理,延迟时间到了就将 fn 加入主进程执行队列,如果队列前面还有没有执行完的代码,则又需要花一点时间等待才能执行到 fn,所以实际的延迟时间会比设置的长。如在 fn 之前正好有一个超级大循环,那延迟时间就不是一丁点了。
(
function
testSetInterval
(
)
{
let
i
=
0
;
const
start
=
Date
.
now
(
)
;
const
timer
=
setInterval
(
(
)
=
>
{
i
+=
1
;
i
===
5
&&
clearInterval
(
timer
)
;
console
.
log
(
`
第
$
{
i
}
次开始
`
,
Date
.
now
(
)
-
start
)
;
for
(
let
i
=
0
;
i
<
100000000
;
i
++
)
{
}
console
.
log
(
`
第
$
{
i
}
次结束
`
,
Date
.
now
(
)
-
start
)
;
}
,
100
)
;
}
)
(
)
;
requestAnimationFrame 并不是定时器,但和 setTimeout 很相似,在没有 requestAnimationFrame 的浏览器一般都是用 setTimeout 模拟。
requestAnimationFrame 跟屏幕刷新同步,大多数屏幕的刷新频率都是 60Hz,对应的 requestAnimationFrame 大概每隔 16.7ms 触发一次,如果屏幕刷新频率更高,requestAnimationFrame 也会更快触发。基于这点,在支持 requestAnimationFrame 的浏览器还使用 setTimeout 做动画显然是不明智的。
在不支持 requestAnimationFrame 的浏览器,如果使用 setTimeout/setInterval 来做动画,最佳延迟时间也是 16.7ms。 如果太小,很可能连续两次或者多次修改 dom 才一次屏幕刷新,这样就会丢帧,动画就会卡;如果太大,显而易见也会有卡顿的感觉。
有趣的是,第一次触发 requestAnimationFrame 的时机在不同浏览器也存在差异,Edge 中,大概 16.7ms 之后触发,而 Chrome 则立即触发,跟 setImmediate 差不多。按理说 Edge 的实现似乎更符合常理。
但相邻两次 requestAnimationFrame 的时间间隔大概都是 16.7ms,这一点是一致的。当然也不是绝对的,如果页面本身性能就比较低,相隔的时间可能会变大,这就意味着页面达不到 60fps。
Promise
Promise 是很常用的一种异步模型,如果我们想让代码在下一个事件循环执行,可以选择使用 setTimeout(0)、setImmediate、requestAnimationFrame(Chrome) 和 Promise。
而且 Promise 的延迟比 setImmediate 更低,意味着 Promise 比 setImmediate 先执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function
testSetImmediate
(
)
{
const
label
=
'setImmediate'
;
console
.
time
(
label
)
;
setImmediate
(
(
)
=
>
{
console
.
timeEnd
(
label
)
;
}
)
;
}
function
testPromise
(
)
{
const
label
=
'Promise'
;
console
.
time
(
label
)
;
new
Promise
(
(
resolve
,
reject
)
=
>
{
resolve
(
)
;
}
)
.
then
(
(
)
=
>
{
console
.
timeEnd
(
label
)
;
}
)
;
}
testSetImmediate
(
)
;
testPromise
(
)
;
可以肯定的是,在各 JS 环境中,Promise 都是最先执行的,setTimeout(0)、setImmediate 和 requestAnimationFrame 顺序不确定。
process.nextTick
process.nextTick 是 Nodejs 的 API,比 Promise 更早执行。
事实上,process.nextTick 是不会进入异步队列的,而是直接在主线程队列尾强插一个任务,虽然不会阻塞主线程,但是会阻塞异步任务的执行,如果有嵌套的 process.nextTick,那异步任务就永远没机会被执行到了。
使用的时候要格外小心,除非你的代码明确要在本次事件循环结束之前执行,否则使用 setImmediate 或者 Promise 更保险。
—– Promise 是很常用的一种异步模型,如果我们想让代码在下一个事件循环执行,可以选择使用 setTimeout(0)、setImmediate、requestAnimationFrame(Chrome) 和 Promise
其中貌似有点问题,promise 的异步代码不会在【下一个事件循环】执行,而是在【本次事件循环】结束之后执行。
@sunyuhui
是的,nextTick 和 promise 都属于 microtask,是在 idle 阶段执行的,所以优先级会比 timeout,timeinterval 这些在 check 阶段执行的要早
有多少是空心 www.kxhtml.com 这样的众包网站:价格标准 100 元/页面,专属项目经理管这管那质量,不小心点了退款钱到了,靠谱技工为个好评就差做牛做马了,要不去看看?www.kxhtml.com
发现个问题请教下,setInterval demo 中的在 Chrome 和 nodejs 环境下,执行差别很大。在 Chrome 下前一次结束和下一次开始的时间差一般在 1ms,但是在 nodejs 环境下差别 1000ms 以上
setTimeout 注册的函数 fn 会交给浏览器的定时器模块来管理,延迟时间到了就将 fn 加入主进程执行队列这里是不是写错了。应该是延迟时间到了,就将 fn 加入等待队列吧,而不是主进程执行队列
@TAT.云中飞扬
我的理解是一直在另一个队列中等待的,当主进程执行队列空闲时,另一个队列 的任务才会加入到主进程队列,而不是一开始就加入主进程队列了。这应该就是你所说的 “加入主进程队列也有一个等待的过程”
本文不如这篇 http://www.alloyteam.com/2015/10/turning-to-javascript-series-from-settimeout-said-the-event-loop-model/