前言 大家都知道,Javascript是单线程、顺序执行的,通过事件循环来处理异步。而且稍有开发经验的同学也知道,利用setTimeout、setInterval以及Promise可以延时代码的执行。
顺晟科技
2021-06-16 11:00:45
237
序
众所周知,Javascript是单线程的,按顺序执行,通过事件循环处理异步。而且有一点开发经验的同学也知道,使用setTimeout、setInterval和Promise可以延迟代码的执行。如果在Node.js,大家都会用process.nextTick让代码在下一个周期执行;或者在Vue中,会用Vue.nextTick来保证DOM在执行回调函数之前完全更新。但是如果都放在一起呢?执行的顺序会是什么?
给我一个样本
机灵,你知道下面代码的执行顺序吗?
console.log('脚本开始')
setTimeout(()={
console.log('setTimeout ')
}, 0)
console.log('脚本结束')
看到这个问题,有经验的同学会脱口而出:太简单了,输出以下内容:
脚本开始
脚本结束
定时器
因为setTimeout将被添加到队列中,所以执行将被延迟。确实如此。让我们看看下面的例子。
console.log('脚本开始')
setTimeout(()={
console.log('setTimeout ')
}, 0)
Promise.resolve()。然后(()={
console.log('promise1 ')
}).然后(()={
console.log('promise2 ')
})
console.log('脚本结束')
嗯,这个问题似乎难倒了一些学生,因为他们会有这样一个想法:
谁先执行setTimeout或Promise?听说Promise是异步的,setTimeout是异步的,延迟是0。多好?
你还不明白吗?没关系,先执行,看结果:
脚本开始
脚本结束
承诺1
承诺2
定时器
结果不有趣吗?为什么会先执行Promise?我尽量解释一下。如果解释有误,希望大家多多指导。
众所周知,Javascript是基于事件循环来处理事件的,用户的一些操作会放到事件队列中,Javascript引擎会在适当的时候执行队列中的操作。请注意,我们在这里使用限定词“在适当的时候”,因为Javascript是单线程的。如果某个Javascript执行时间过长,就会阻塞主线程的执行。所以setTimeout并不意味着会被准确执行。
在Javascript引擎中,队列也分为任务队列(也称为宏任务)和微任务队列,微任务优先于任务。常见的点击事件,setImmediate,setTimeout,MessageChannel等。将被放入任务队列,但承诺和变更服务器将被放入微任务队列。同时,当Javascript引擎执行微任务队列时,如果在执行过程中添加了新的微任务,微任务将被添加到前一个微任务队列的尾部,以确保微任务在任务队列之前执行。
这样大家就知道为什么先执行Promise了,因为是Microtask!优先级高,没办法。你可能会问,优先级会高到什么程度?我们可以简单地测量:
const checkDuration=()={
常量开始=日期。现在()
让setTimeoutDuration=0
让承诺率=0
setTimeout(()={
setTimeoutDuration=date . now()-start
}, 0)
Promise.resolve()。然后(()={
承诺=日期。现在()-开始
})
setTimeout(()={
Console.log(`setTimeout需要: ${setTimeoutDuration} `)
Console.log(`Promise取: ${promiseDuration} `)
}, 100)
}
检查持续时间()
我在chrome _ 6494 _ 1 . html ' target=' _ blank ' chrome的控制台执行了很多次,会输出:
SetTimeout取: ^ 1
承诺需要: 0
SetTimeout取: ^ 4
承诺需要: 1
当然,如果结果不固定,重复测试的话,setTimeout的执行时间大概是4ms,Promise的时间大概是1 ms哈哈,其实差不多3ms。前端同学为了拿下这3ms,孜孜不倦,煞费苦心,但是真的很喜欢他们专攻研究的态度!
值得注意的是,Vue。vue中的nexttick也使用这个原则来确保延迟的回调将在下一个DOM更新周期结束后执行。例如,Vue 2.5.2有这样的代码逻辑:
var microTimerFunc
var macroTimerFunc
var useMacroTask=false
if(type of SetIndiCe!=='undefined '是名词(SetInstant)){
macromerfunc=function(){
设置即时(刷新回调);
};
} else if(message CHannel!=='undefined '(
isNative(MessageChannel) ||
//PhantomJS
messagechannel。ToString()==='[对象MessageChannelConstructor]'
)) {
var channel=新消息通道();
var port=channel.port2
频道。端口1。on message=回调;
macrometer func=function(){
港口。post message(1);
};
} else {
/*伊斯坦布尔忽略下一个*/
macrometer func=function(){
设置超时(同花回调,0);
};
}
//确定微任务延迟实施。
/*伊斯坦布尔忽略下一个,$flow-disable-line */
如果(承诺类型!=='未定义'是动词(承诺)){
var p=承诺。resolve();
microTimerFunc=function () {
p .然后(冲水回调);
//在有问题的UIWebViews中答应我,然后没有完全破坏,但是
//它可能会陷入一种奇怪的状态,回调被推入
//微任务队列,但是队列没有被刷新,直到浏览器
//需要做一些其他的工作,比如处理一个定时器。因此我们可以
//"强制"通过添加空计时器来刷新微任务队列。
if(isIOS){ setTimeout(noop);}
};
} else {
//回退到宏
microTimerFunc=macroTimerFunc
}
在Vue,用宏任务就是我们上文说的任务。可见执行的时机是:
任务(宏任务)队列中:设置即时消息通道设置超时
微任务队列中:直接用了答应我,新版本中弃用了突变观察器,因为其兼容性不好
扯了这么多,大家应该知道原因了吧?
再来个样例
为了巩固大家对微任务的列举,我们再看一个例子
div class='outer '
div class='inner'/div
/div
//获取数字正射影像图
const outer=document。查询选择器(' .外部)
const inner=document。查询选择器(' .内部)
//利用MuationObserver监听数字正射影像图的变化
新的突变服务器(()={
console.log('突变)
}).观察(外部,{
属性:为真
});
//事件处理
const onClick=()={
console.log('单击)
setTimeout(()={
console.log("超时")
}, 0)
Promise.resolve().然后(()={
console.log('promise ')
})
外部。SetAttribute(' data-random ',Math.random())
}
//事件绑定
inner.addEventListener('click ',onClick)
outer.addEventListener('click ',onClick)
如果我们点击inneer区域,输出内容为什么呢?如果你理解了上文的内容,就会知道输出结果为:
点击
承诺
使突变
点击
承诺
使突变
超时
超时
好,今天就分享在这里。下篇文章,我们聊聊Node.js里面的事件。比如上文我们还没提到setImmediate呢?这个东西只在工业管理学(工业工程)里面支持,但是在Node.js里面是支持的,而且Node.js里面还有一个Process.nextTick。下次我们再聊聊。
16
2021-06
16
2021-06
16
2021-06
16
2021-06
16
2021-06
16
2021-06