18910140161

Javascript执行顺序是怎样的

顺晟科技

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。下次我们再聊聊。

相关文章
我们已经准备好了,你呢?
2024我们与您携手共赢,为您的企业形象保驾护航