js异步机制

428 阅读5分钟

单线程与多线程

  • JavaScript是单线程: 按顺序执行每一行代码
  • 目前最为流行的浏览器为: Chrome,IE,Safari,FireFox,Opera,浏览器的内核是多线程

一个浏览器通常由以下几个常驻的线程:

  1. 渲染引擎线程: 顾名思义,该线程负责页面的渲染
  2. JS引擎线程: 负责JS的解析和执行
  3. 定时触发器线程: 处理定时事件,比如setTimeout, setInterval
  4. 事件触发线程: 处理DOM事件
  5. 异步http请求线程: 处理http请求

虽然JavaScript是单线程的,可是浏览器内部不是单线程的。一些I/O操作、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其他线程来完成的

渲染引擎线程和JS引擎线程

渲染引擎线程和JS引擎线程是不能同时进行的。渲染引擎线程在执行任务的时候,JS引擎线程会被挂起。因为JS可以操作DOM,若在渲染中JS处理了DOM,浏览器可能就不知所措了

渲染引擎

就是如何渲染页面,Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trident引擎,FireFox用的是Gecko引擎。不同的引擎对同一个样式的实现不一致,就导致了经常被人诟病的浏览器样式兼容性问题

JS引擎

可以说是JS虚拟机,负责JS代码的解析和执行。通常包括以下几个步骤:

  • 词法分析:将源代码分解为有意义的分词
  • 语法分析:用语法分析器将分词解析成语法树
  • 代码生成:生成机器能运行的代码
  • 代码执行

不同浏览器的JS引擎也各不相同,Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。

消息队列与事件循环

消息队列

image.png

  • 左边的栈存储的是同步任务,就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类
  • 右边的堆用来存储声明的变量、对象
  • 下面的队列就是消息队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和setTimeout中待执行的事件,每个异步任务都和回调函数相关联

事件循环

image.png

  • JS引擎线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取消息队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务。
  • JS引擎线程从消息队列中读取任务是不断循环的,每次栈被清空后,都会在消息队列中读取新的任务,如果没有新的任务,就会等待,直到有新的任务

事件循环详解

事件循环中分为宏任务队列和微任务队列

宏任务队列

  • script(整体代码)
  • setTimeout
  • setInterval
  • setImmediate(定时器-node环境独有)
  • I/O(网络I/O,文件I/O)
  • UI交互(用户交互的回调等事件,UI渲染事件(DOM解析,布局计算,绘制))
  • postMessage(跨源通信)
  • MessageChannel(消息通道-react的fiber用到)

微任务队列

  • promise.then/catch/finally里的回调函数
  • await下一行之后的所有代码(await后面的表达式会立即执行)
  • // process.nextTick 的优先级高于 promise(即会比promise更早执行)
  • process.nextTick(定时器-node环境独有)
  • MutationObserver(监听DOM变动)
  • Object.observe(已废弃)

执行顺序

  1. 先执行宏任务script
  2. 执行的过程中发现宏任务或微任务,都分别存在各自队列中
  3. 执行完宏任务script后,先依次执行微任务队列中的所有微任务
  4. 执行微任务的过程中,发现宏任务或微任务,继续存在各自队列后面
  5. 直至微任务队列清空,才能执行下一个宏任务
  6. 执行下一个宏任务,重复以上步骤

题目一

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');
}, 0)

async1();

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

console.log('script end');
// 宏任务script
script start
async1 start
async2
promise1
script end
// 清空所有微任务(async1 end,promise2)
async1 end
promise2
// 宏任务setTimeout
setTimeout

题目二

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  new Promise(function(resolve) {
    console.log('promise1');
    resolve();
  }).then(function() {
    console.log('promise2');
  });
}

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
  console.log('promise3');
  resolve();
}).then(function() {
  console.log('promise4');
});

console.log('script end');
// 宏任务script
script start
async1 start
promise1
promise3
script end
// 清空所有微任务(promise2,async1 end,promise4)
promise2
async1 end
promise4
// 宏任务setTimeout
setTimeout

题目三

async function async1() {
  console.log('async1 start');
  await async2();
  setTimeout(function() {
    console.log('setTimeout1');
  }, 0)
}
async function async2() {
  setTimeout(function() {
    console.log('setTimeout2');
  }, 0)
}

console.log('script start');

setTimeout(function() {
  console.log('setTimeout3');
}, 0)

async1();

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

console.log('script end');
// 宏任务script
script start
async1 start
promise1
script end
// 清空所有微任务(promise2)
promise2
// 宏任务setTimeout3,setTimeout2,setTimeout1
setTimeout3
setTimeout2
setTimeout1

题目四

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

console.log('script start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
  console.log('promise1')
})

async1()

let promise2 = new Promise((resolve) => {
  resolve('promise2.then')
  console.log('promise2')
})

promise2.then((res) => {
  console.log(res)
  Promise.resolve().then(() => {
    console.log('promise3')
  })
})

console.log('script end')
// 宏任务script
script start
async1 start
async2
promise2
script end
// 清空所有微任务(promise1,async1 end,promise2.then,promise3)
promise1
async1 end
promise2.then
promise3
// 宏任务setTimeout
setTimeout

参考