关于JS事件循环

JavaScript 是单线程的

JS 是一门单线程的非阻塞的脚本语言,这表示在同一时刻最多也只有一个代码段执行。

为什么 JavaScript 是单线程的

如果 JS 是多线程的,因为 JS 有 DOM API 可以操作 DOM,如果同时开了两个线程同时操作 DOM 的话,一个线程删除了当前的 DOM 节点,另一个线程要操作当前的 DOM,那么就会有矛盾到底以哪个线程为主。为了避免这种情况出现,JS 就被设计为单线程,而且单线程执行效率高。现在虽然也有 web worker 标准的出现,但它也有很多限制,受主线程控制,是主线程的子线程。

JS 如何处理异步任务

JS 是单线程,那么非阻塞怎么体现呢?如果 JS 是阻塞的,那么 JS 发起一个异步 IO 请求,在等待结果返回的这个时间段,后面的代码就无法执行了,而 JS 主线程和渲染进程是互斥的,因此可能造成浏览器假死的状态。事实 JS 是非阻塞的,那它要怎么实现异步任务呢,靠的就是事件循环。

事件循环

事件循环就是通过异步执行任务的方法来解决单线程的弊端的。

  1. 一开始整个脚本作为一个宏任务执行
  2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  3. 当前宏任务执行完出队,读取微任务列表,有则依次执行,直到全部执行完
  4. 执行浏览器 UI 线程的渲染工作
  5. 检查是否有 Web Worker 任务,有则执行
  6. 执行完本轮的宏任务,回到第 2 步,继续依此循环,直到宏任务和微任务队列都为空

宏任务与微任务

JS 引擎把所有任务分成两类,一类叫宏任务(macroTask),一类叫微任务(microTask)

宏任务

  • script(整体代码)
  • setTimeout/setInterval
  • I/O
  • UI 渲染
  • postMessage
  • MessageChannel
  • requestAnimationFrame
  • setImmediate(Node.js 环境)

微任务

  • new Promise().then()
  • MutaionObserver
  • process.nextTick(Node.js 环境)

经典题目 1

简单介绍后,接下来让我们来看几道经典题目吧:

console.log('script start')

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

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

console.log('script end')
  1. 整体 script 作为第一个宏任务进入主线程,输出script start
  2. 遇到 setTimeout,setTimeout 为宏任务,加入宏任务队列
  3. 遇到 Promise,其 then 回调函数加入到微任务队列;第二个 then 回调函数也加入到微任务队列
  4. 继续往下执行,输出script end
  5. 检测微任务队列,输出promise1promise2
  6. 进入下一轮循环,执行 setTimeout 中的代码,输出setTimeout

最后执行结果为:

script start
script end
promise1
promise2
setTimeout

经典题目 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')
}, 0)
async1()
new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('promise2')
})
console.log('script end')
  1. 整体 script 作为第一个宏任务进入主线程,代码自上而下执行,执行同步代码,输出 script start
  2. 遇到 setTimeout,加入到宏任务队列
  3. 执行 async1(),输出async1 start;然后遇到await async2(),await 实际上是让出线程的标志,首先执行 async2(),输出async2;把 async2() 后面的代码console.log('async1 end')加入微任务队列中,跳出整个 async 函数。(async 和 await 本身就是 promise+generator 的语法糖。所以 await 后面的代码是微任务。)
  4. 继续执行,遇到 new Promise,输出promise1,把.then()之后的代码加入到微任务队列中
  5. 继续往下执行,输出script end。接着读取微任务队列,输出async1 endpromise2,执行完本轮的宏任务。继续执行下一轮宏任务的代码,输出setTimeout

最后执行结果为:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

可以知道,浏览器只保证 requestAnimationFrame 的回调在重绘之前执行,但没有确定的时间,何时重绘由浏览器决定。

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值