js异步机制

前言

  • js引擎不是独立运行的,它运行在宿主环境中,这个环境可以是浏览器、可以是服务器,或者其他硬件设施。所以在浏览器的帮助下,js作为一种单线程语言,可以实现异步操作。
  • 浏览器内核是多线程的,几个常驻的线程:渲染引擎线程、js引擎线程、定时触发器线程、事件触发线程、异步http请求线程。

并发模型

MDN-并发模型

js异步机制_第1张图片

  • 左边的栈存储的是同步任务。右边的堆用来存储声明的变量、对象。下面的队列就是任务队列,一旦某个异步任务有了响应就会被推入队列中。每个异步任务都和一个回调函数相关联。
  • 一个js程序的单线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取任务队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务,执行完毕。
  • 单线程从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有新的任务,就会等待,直到有新的任务,这就叫任务循环或者事件循环

事件循环(Event Loop)

事件循环的大致流程如下:

  1. 主线程执行所有同步任务,形成一个执行栈(并发模型中的stack)。
  2. 主线程执行同步任务的同时,子线程执行异步任务,并将相应的结果(事件)放入任务队列。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,把任务队列中事件相应的回调函数压入栈内开始执行。
  4. 执行回调后,栈空,继续重复第三步,形成一个循环。

Microtask与Macrotask(task)

Microtask和Macrotask(task)是异步任务的两个分类。

macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver

事件循环每次只会入栈一个 macrotask任务 ,主线程会先检查 microtasks 队列并完成里面的所有任务后再执行 macrotask(task)。

举个栗子:

console.log('script start');
setTimeout(function() {
  console.log('setTimeout1');
})
}, 0);
setTimeout(function() {
  console.log('setTimeout2');
}, 0);
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
console.log('script end');

结果是:

"script start"
"script end"
"promise1"
"promise2"
"setTimeout1"
"setTimeout2"
可以看到,macrotask是在microtask全部执行后才执行的。

再举一个栗子:

console.log('script start');
setTimeout(function() {
  console.log('setTimout1');
  setTimeout(function() {
    console.log('setTimout2');
  }, 0);
  Promise.resolve().then(function() {
    console.log('promise3');
  })
}, 0);
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
console.log('script end');

结果是:

"script start"
"script end"
"promise1"
"promise2"
"setTimout1"
"promise3"
"setTimout2"
可以发现,在执行完setTimeout1之后没有继续执行setTimeout2,而是去执行了promise3,这也验证了:每执行一个macrotask任务之前都会执行完当前的microtask队列。
注意:执行完microtask任务后进行的步骤是渲染。

注: 代码示例参考:https://v.youku.com/v_show/id...

你可能感兴趣的:(js异步机制)