【JS不全记录】- JS的执行机制 - Event Loop

86 阅读4分钟

JavaScript是一个单线程的脚本语言

单线程就意味着,所有的任务需要排队。前一个任务结束才会执行后一个任务。

任务类型

在JS单线程中运行的任务分为两种:同步任务异步任务

  • 同步任务:在主线程中排队执行的任务
    • 只有前一个任务执行完毕,才能执行后一个任务
  • 异步任务:在Event Table中执行,只有任务队列通知主线程,某个异步任务可以执行时,该任务才会进入主线程执行
同步和异步执行场所不同。
同步任务在【主线程】中执行
异步任务在【Event Table】中执行

事件循环(Event Loop)机制 - 同/异步层面

  1. JS将任务分为同步任务异步任务,同步任务进入主线程中,异步任务进入Event Table进行回调函数注册。
  2. 当异步任务的触发条件满足,将回调函数从 Event Table 压入 Event Queue中。
  3. 主线程中的同步任务执行完毕,会去Event Queue读取异步的回调函数。
  4. 以后,只要主线程空了,就会去Event Queue中读取回调函数。
JS引擎的 monitoring process 进程会持续不断的检查主线程执行栈是否为空,一旦为空,就会去 Event Queue 中读取异步的回调函数

一个例子

setTimeout(cb, 100ms) // 当100ms后,就将cb压入Event Queue中。
ajax(请求条件, cb) // 当http请求发送成功后,将cb压入Event Queue中。

详解异步任务

异步任务中包含 宏任务(macrotask)微任务(microtask)

执行顺序

主线程任务 -> 微任务 -> 宏任务

如果宏任务里还有微任务,就继续执行宏任务里的微任务
如果宏任务中的微任务还有宏任务,就依次执行

主线程任务 -> 微任务 -> 宏任务 -> 宏任务里的微任务 -> 宏任务里的微任务中的宏任务

同级下,微任务 权重优于 宏任务

宏任务和微任务都有各自的任务队列(Event Queue):宏任务队列 和 微任务队列

宏任务

  • script(可以理解为外层同步代码)
  • setTimeout/setInterval
  • ajax
  • setImmediate、I/O(node.js)
  • UI rendering

微任务

  • Promise.then
  • process.nextTick(node.js中的一个异步操作)
  • Mutation Observer(H5中监听DOM节点变化的)
  • Object.observe(已废弃,Proxy对象代替)

事件循环(Event Loop)机制 - 宏/微任务层面

  1. 创建全局调用栈,script作为宏任务执行
  2. 此时同步任务立即执行,异步任务根据任务类型分别注册到宏/微任务队列中
  3. 同步任务执行完毕,查看微任务队列
    • 若存在微任务,将微任务队列全部执行
    • 若无微任务,查看宏任务队列
      • 执行第一个宏任务
      • 执行完毕,看是否存在微任务,如此重复,指导宏任务队列为空

Event Loop - 事件循环

2.jpg

async与await

async-异步,await-async wait

综合理解:async是用来声明一个异步方法await是用来等待异步方法的执行

async

async返回一个promise对象,下面两种方法是等效的

function func() {
    return Promise.resolve('test);
}

async function asyncf() {
    return 'test
}

await

await命令后面是一个promise对象,则返回该对象的结果。如果不是,则返回对应的值

async function f() {
    return await 'test'
    // 等同于 return 'test'
}

f().then(v=>console.log(v)) // test

【注意】不管await后面跟着什么,await都会阻塞后面的代码!

async function fn1() {
    console.log(1);
    await fn2();
    console.log(2); // 被阻塞
}

async function fn2() {
    console.log('fn2')
}

fn1();
console.log(3);

/*
    上述例子中,await会阻塞下面的代码(即加入微任务队列)
    执行结果:1,fn2,3,2
*/

一个经典的例子

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('settimeout')
})

async1()

new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('promise2')
})

console.log('script end')

输出结果分析:
执行到 console.log('script start')时,输出 script start
遇到 定时器setTimeout,压入 【宏任务队列】
遇到 async1(),先打印 async1 start
遇到 await async2(),输出 async2。await后面的(async1 end)压入 【微任务队列】,直接跳出执行同步代码
跳到 new Promise(),输出promise1。
遇到 then(),压入 【微任务队列】
最后一行直接输出 script end
目前为止 同步任务 执行完了,开始执行微任务
执行await()下面的代码,输出 async1 end
执行下一个微任务,即then(),输出 promise2
到此为止,微任务队列中的任务已经执行完毕,接下来执行 宏任务
执行setTimeout()中的代码,输出 settimeout

全部输出:script start、async1 start、async2、promise1、script end、async1 end、promise2、settimeout