javascript异步机制(一)

170 阅读3分钟

什么是javascript异步?

js代码中有很多异步,比如回调函数,事件处理函数,ajax请求等。想要深入了解js的异步执行机还要从js的执行机制说起。

javascript是一门单线程语言。js有一个主线程main thread和调用栈call-stack,所有任务(js中每个语句都可以理解为一个任务)都会放在调用栈中等待主线程来执行。

对于同步任务,会立即压入调用栈中,并由主线程执行,没有太大问题。

console.log('a')  // a
console.log('b')  // b

对于异步任务,就有些复杂了,我们循序渐进,一步一步进行探讨。

  1. setTimeout 定时器典型的js异步函数
setTimeout(function(){
    console.log('a')
},1000) 
console.log('b')
// 代码执行后打印顺序为 b a

问题不大,简单的理解为setTimeout的回调是在1s后执行。所以先打印b,1秒后打印a。好,接着看...

console.log('a',new Date().getSeconds())
setTimeout(function(){
    console.log('b', new Date().getSeconds())
},1000)
for(var i = 0; i < 100000; i++){
    console.log('循环')
}
// 打印如下:
a 20
100000 循环
undefined
b 24

打印a和打印b之间间隔了4秒,也就是说并非在打印a后的1秒之后打印b。js的异步是不是有点意思?别着急再看下面案例

setTimeout(function(){
    console.log('a')
},0)
console.log('b')
// 打印如下
b
a

0秒不是立即执行吗?显然不是。

在此,需要深入了解js的执行机制了:

'图示'
任务在进入执行栈之前会做个判断,判断当前是同步任务还是异步任务。同步任务直接由主线程进行执行。异步任务会进入事件列表event-table,异步任务上的回调会进入到event queue事件队列。等待主线程没有任务时,会从事件队列中调取任务到主线程执行。

到此可以分析上面两个案例:

  1. 为什么1秒后的回调会在4秒后执行?0秒的回调并非立即执行?

1s的回调时,只是将此回调注册到事件队列evnet-queue,真正被执行是在主线程将同步任务都完成后再从事件队列调取任务执行。

到此应该感觉到:了解事物的本质才能看清事物的表象!....可是,事情还远远没完...

console.log('a')
setTimeout(function(){
    console.log('b')
},1000)
new Promise((resolve,reject) => {
    console.log('c')
    resolve()
}).then(() => {
    console.log('d')
})
// 打印顺序如下   a  c  d  b

好像也是符合上面的逻辑,当把 setTimeout 的1000 改成 0 时,会发现结果并没有变化。由此产生一个新的问题:为啥then中的回调会比setTimeout的回调先执行,何况setTimeout为0秒?

在此要引入一个概念,宏任务MacroTask和微任务MicroTask

  1. 宏任务包括:setTimeout / setInterval / IO操作 / script代码块 / js同步任务
  2. 微任务包括: Process.nextTick / Promise.then / catch / finally / MutationObserver

在此可以发现一个规律:

  1. 所有的微任务都是异步的;但并非所有的的异步都是宏任务。
  2. 所有的同步任务都是宏任务。

我们已知道js遇到异步任务时,会将异步任务注册到event-table事件列表,并将异步任务的回调函数注册到event-queue事件队列,等待主线程执行。但当js遇到的异步任务是微任务时,微任务具有优先执行权。

到此,也解释了上述例子的原因。 js的执行原理到此告一段落。若有错误的理解和新的内容会接着修改。