JS运行机制(事件循环、同步、异步)

154 阅读4分钟

JS单线程

进程和线程(浏览器多进程和JS单线程)一文中已经提到,JavaScript是一门单线程的语言。因此,JavaScript在同一时间内只能做一件事,单线程意味着,如果在同一时间有多个任务(每一段JS程序都可以看做是一个任务),这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。比如说下面这段代码

function fun1() {
    console.log(1); 
} 
function fun2() {
    console.log(2); 
} 
fun1(); 
fun2(); 

// 输出 1 2

因为代码是从上到下依次执行的,执行完fun1(),才继续执行fun2(),因此会依次输出1,2。但是如果fun1()中的代码执行的是读取文件或者ajax操作,文件的读取和数据的获取都需要一定时间,如果我们完全等fun1()执行完才继续执行fun2(),对于用户而言,就意味着页面"卡死",严重影响用户体验。

JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务。因此,就有了同步任务和异步任务的概念。

同步任务和异步任务

同步任务(synchronous):在主线程上排队执行的任务。只有前一个任务执行完毕,才能继续执行下一个任务。

异步任务(asynchronous):不进入主线程,而进入 任务队列 的任务。只有某个异步任务可以执行了,该任务才会进入主线程。

在任务队列中,其实还分为 宏任务队列(Task Queue)和微任务队列(Microtask Queue),对应的里面存放的就是 宏任务 和 微任务 。

常见宏任务:setTimeout、setInterval、 setImmediate(Node.js 环境)

常见微任务:Promise.then、process.nextTick(Node.js 环境)

个人的理解是,宏任务和微任务都是异步任务。 也有些文章将script整体代码归类到宏任务,而我理解它是第一执行的主线程,只能说每个人的理解方式不同,没有找到统一标准。

事件循环(Event Loop)

Event Loop主要包含三大块 :一个函数执行栈、一个宏任务队列和一个微任务队列。

同步任务都在主线程上执行,形成一个执行栈,在JS引擎上的任务(也就是主线程上的任务),只有前一个任务执行完毕,才能执行后一个任务。当JS引擎中的任务执行完成了,就会去查询异步的任务队列中是否有可以执行的任务。一旦这些异步任务可以执行了,就会将它添加到JS引擎中,以此循环。

具体运行机制如下:

  1. 在执行栈上的任务,只有前一个任务执行完毕,才能执行后一个任务;
  2. 当执行栈上的任务执行完毕之后,检查微任务队列,取出队列中所有事件压入执行栈执行;
  3. 完毕之后,检查宏任务队列,取出一个事件压入执行栈执行;
  4. 完毕之后,再次检查微任务队列,取出所有的事件压入执行栈执行;
  5. 完毕之后,检查宏任务队列,取出一个事件压入执行栈执行,重复该过程。

由于JS引擎从“任务队列”中读取事件的这整个过程是不断循环的,所以这种运行机制又称为 Event Loop(事件循环)

注意:对于宏任务每次只从宏任务队列种取一个事件压入执行栈执行,微任务每次从微任务队列种取出所有的事件压入执行栈执行。可以简单理解为 微任务的优先级高于宏任务

经典问题:

console.log('start');

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

new Promise((resolve) => {
  console.log('Promise');
  resolve()
}).then(() => {
  console.log('then1');
}).then(() => {
  console.log('then2');
});

console.log('end');

image.png

  • 主线程:start promise end
  • 宏任务:setTimeout
  • 微任务:then1 then2

任务队列既然是队列,就遵循先进先出原则,根据上述js运行机制,最终依次输出:

start promise end then1 then2 setTimeout

总结

  1. 计算机领域中的同步(Synchronous)和异步(Asynchronous)和我们生活中的同步和异步的概念是恰好相反的,感觉是翻译要背这个锅。生活中的同步,突出的是“同”,相同的步伐,一起行动,而计算机中的同步则是排队等待执行。在开发中无论是前端还是后端,同步和异步都是同种概念。

  2. 同步需要等待所有步骤执行完了才能继续往下执行,异步只需要发起调用后就可以继续其他逻辑。

  3. 同步会造成线程阻塞,但是异步执行不会造成自己的线程阻塞。

  4. 同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性,我们要根据不同的需求去写代码。



【参考】www.cnblogs.com/Yellow-ice/…