1. 关于setTimeout出现4ms的延迟
众所周知,setTimeout第二个参数延迟时间
并不是浏览器渲染完毕后多少秒执行第一个参数回调,而是多少时间之后将回调放入执行栈。只要setTimeout形成的嵌套足够多,这样定时器线程来不及处理,执行栈就足够深,就会导致一定的延迟,延迟最稳定在4ms左右。
本文做了个实验,验证一下:
let a = Date.now();
setTimeout(()=>{
let b=Date.now();
console.log(b-a);
setTimeout(()=>{
let c=Date.now();
console.log(c-b);
setTimeout(()=>{
let d=Date.now();
console.log(d-c);
setTimeout(()=>{
let e=Date.now();
console.log(e-d);
setTimeout(()=>{
let f=Date.now();
console.log(f-e);
setTimeout(()=>{
let g=Date.now();
console.log(g-f);
setTimeout(()=>{
let h=Date.now();
console.log(h-g)
},0)
},0)
},0)
},0)
},0)
},0)
},0)
复制代码
得出结果如下:
可见最后几次时间差都在4ms左右,有一定的缺陷。
现在有一种比较好的方案处理这种延迟,那就是使用window.postMessage()
原理来实现0毫秒定时器。接下来我们先简要介绍一下postMessage()
2. postMessage()简要介绍
postMessage
主要是用来处理页面中嵌套iframe跨域的问题,它的通信原理类似于TCP三次握手,不但能解决各种跨域问题,而且实时性高。
下面是一个简单的demo,a.html
和b.html
使用postMessage()
通信。
//a.html
<body style="border: 1px solid red;">
<h1>this is A</h1>
<iframe src="./b.html" id="myFrame"></iframe
>
</body>
<script>
var frame = document.getElementById("myFrame");
frame.contentWindow.postMessage("A给你发消息啦", "*");
window.addEventListener(
"message",
function (ev) {
var data = ev.data;
console.log(data)
},
false
);
</script>
复制代码
//b.html
<body style="border: 2px solid blue;">
<h1>this is B</h1>
</body>
<script>
window.addEventListener(
"message",
function (ev) {
console.log("收到A的消息啦")
var data = ev.data;
console.log(data)
},
false
);
parent.postMessage("B给你发消息啦", "*");
</script>
复制代码
运行的结果:
3.使用postMessage()实现setTimeout()
代码如下:
(function () {
var timeouts = [];
var messageName = '发送给自己任何消息,可以为空';
function setZeroTimeout(fn) {
timeouts.push(fn);
window.postMessage(messageName, '*'); //发送
}
function handleMessage(event) {
event.stopPropagation();
if (timeouts.length > 0) {
var fn = timeouts.shift();
fn();
}
}
window.addEventListener('message', handleMessage, true);//监听发送的消息,执行handleMessage
window.setZeroTimeout = setZeroTimeout;
})();
复制代码
最后,我们调用自己写的setZeroTimeout()做一下类似setTimeout()嵌套的实验:
let a = Date.now();
setZeroTimeout(()=>{
let b=Date.now();
console.log(b-a);
setZeroTimeout(()=>{
let c=Date.now();
console.log(c-b);
setZeroTimeout(()=>{
let d=Date.now();
console.log(d-c);
setZeroTimeout(()=>{
let e=Date.now();
console.log(e-d);
setZeroTimeout(()=>{
let f=Date.now();
console.log(f-e);
setZeroTimeout(()=>{
let g=Date.now();
console.log(g-f)
})
})
})
})
})
})
复制代码
运行结果如下:
第一次的时间差6s是包括了匿名函数自执行的时间,其余时间差都是在0ms~1ms
,可见postMessage()可以实现我们最初的目的,弥补了setTimeout()
4ms的缺陷。
4. 总结:postMessage
用在什么地方
- React时间切片中使用到了
postMessage
定时器,具体可以看这篇文章《React Scheduler 为什么使用 MessageChannel 实现》 - 对页面性能要求较高的地方,使用
postMessage
至少可以节省4ms左右的时间。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END