前端知识点之JS的同步与异步&&EventLoop

JS的同步与异步&&EventLoop

JS为何是单线程的?

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成这门语言的核心特征,将来也不会改变。

注:所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。

在浏览器渲染中的JS引擎线程(JS内核)中也提到了相关的知识。

计算机的同步与异步

计算机的同步

同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。
可以理解为同步需要进行等待,在上一步跑完后下一步才能继续执行。
一般用于流程性比较强的程序,比如用户登录,需要对用户验证完成后才能登录系统。

【特点】:

  • 同步是阻塞模式;
  • 同步是按顺序执行,执行完一个再执行下一个,需要等待,协调运行;

计算机的异步

异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
异步不需要进行等待,不管其他进程状态,直接就可以执行。比如加载页面。

【特点】:

  • 异步是非阻塞模式,无需等待;
  • 异步是彼此独立,在等待某事件的过程中,继续做自己的事,不需要等待这一事件完成后再工作。线程是异步实现的一个方式。

js单线程为什么会有’异步’问题

如果JS中不存在异步,只能自上而下执行,万一上一行解析时间很长,那么下面的代码就会被阻塞。 对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验

js单线程又是如何实现异步的呢?

通过事件循环(event loop) 实现’异步’

宏任务和微任务

宏任务

macrotask,也叫 tasks,主要的工作如下

  • 创建主文档对象,解析HTML,执行主线或者全局的javascript的代码,更改url以及各种事件。
  • 页面加载,输入,网络事件,定时器。从浏览器角度看,宏任务是一个个离散的,独立的工作单元。
  • 运行完成后,浏览器可以继续其他调度,重新渲染页面的UI或者去执行垃圾回收

一些异步任务的回调会以此进入 macrotask queue(宏任务队列),等等后续被调用,这些异步函数包括:

  • setTimeout
  • setInterval
  • setImmediate (Node)
  • requestAnimationFrame (浏览器)
  • I/O
  • UI rendering (浏览器)

微任务

microtask,也叫 jobs,注意的工作如下

  • 微任务是更小的任务,微任务更新应用程序的状态,但是必须在浏览器任务继续执行其他任务之前执行,浏览器任务包括重新渲染页面的UI。
  • 微任务包括Promise的回调函数,DOM发生变化等,微任务需要尽可能快地,通过异步方式执行,同时不能产生全新的微任务。
  • 微任务能使得我们能够在重新渲染UI之前执行指定的行为,避免不必要的UI重绘,UI重绘会使得应用状态不连续

另一些异步回调会进入 microtask queue(微任务队列) ,等待后续被调用,这些异步函数包括:

  • process.nextTick (Node)
  • Promise.then()
  • catch
  • finally
  • Object.observe
  • MutationObserver

这里有一点需要注意的:Promise.then()new Promise(() => {}).then() 是不同的,前面的是一个微任务,后面的 new Promise() 这一部分是一个构造函数,这是一个同步任务,后面的 .then() 才是一个微任务,这一点是非常重要的。

EventLoop

最开始有一个执行栈,当执行到带有异步操作的宏任务的时候,比如 setTimeout 的时候就会将这个异步任务存在背景线程里面,待本次的事件执行完成以后再去执行微任务。即图中 Stack --> Background Thread

但是需要注意到,从 Stack --> Microtask Queue 还有一条路线,意思就是在当前这轮的任务中还有执行微任务的操作。

当前轮的微任务优先于宏任务异步操作先执行,执行完成到 loop 中,进入到下一轮。

下一轮执行之前的宏任务的异步操作,比如 setTimeout

此时,如果这个异步任务中还有微任务,那么就会执行完成这个微任务,在执行下一个异步任务。就这样一次的循环。

setTimeout( () => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then( () => {
  console.log(3)
})

Promise.resolve(5).then(() => console.log(5))

console.log(2)

整个这一串代码我们所在的层级我们看做一个任务,其中我们先执行同步代码。

第一行的 setTimeout 是异步代码,跳过,来到了 new Promise(...) 这一段代码。

前面提到过,这种方式是一个构造函数,是一个同步代码,所以执行同步代码里面的函数,即 console.log(1)

接下来是一个 then 的异步,跳过。在往下,是一个Promise.then() 的异步,跳过。

最后一个是一段同步代码 console.log(2)。所以,这一轮中我们知道打印了1, 2两个值。接下来进入下一步,即之前我们跳过的异步的代码。

从上午下,第一个是 setTimeout,还有两个是 Promise.then()

setTimeout 是宏任务的异步,Promise.then()是微任务的异步,微任务是优先于宏任务执行的,所以,此时会先跳过 setTimeout 任务,执行两个 Promise.then() 的微任务。

所以此时会执行 console.log(3)console.log(5) 两个函数。最后就只剩下 setTimeout 函数没有执行,所以最后执行 console.log(4)

下面再来个例子

setTimeout( () => {
  new Promise(resolve => {
    resolve()
    console.log(4)
  }).then(() => {
    console.log(7)
  })
})

new Promise(resolve => {
  resolve()
  console.log(1)
}).then( () => {
  console.log(3)
})

setTimeout( () => {
  Promise.resolve(6).then(() => console.log(6))
  new Promise(resolve => {
    resolve()
    console.log(8)
  }).then(() => {
    console.log(9)
  })
})

Promise.resolve(5).then(() => console.log(5))

console.log(2)

// 1 2 3 5 4 7 8 6 9

console.log(8)
}).then(() => {
console.log(9)
})
})

Promise.resolve(5).then(() => console.log(5))

console.log(2)

// 1 2 3 5 4 7 8 6 9


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PrototypeONE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值