JS是一门单线程的语言,这意味着它同一时间只能执行一个任务。那么当JS遇到需要长时间执行的代码时,后面的代码是不是会一直处于等待被执行的状态呢?如果是这样的话,岂不是有可能会给用户带来非常不好的体验?然而事实是并不会这样,Event-Loop(事件循坏)能够很好的解决这个问题,要知道这个问题的答案就需要我们深入的了解去JS这门语言中代码是如何被执行的。
线程和进程的概念
- 进程指的是cpu在运行指令和保存上下文所需的时间
- 线程是进程中更小的单位,指的是一段指令执行所需的时间
以浏览器新开一个tab页为例
这个操作是一个进程,这个进程中会包含多个线程,如:
- 渲染线程
- http请求线程
- js引擎线程
在这个进程中:
- 线程之间是可以一起工作的
- 但是渲染线程 和 js引擎线程 是互斥的
JS为什么会被设计成一门单线程语言
- 节约内存
- 没有锁的概念,节约了上下文切换的时间
JS中通过事件循环和异步编程来弥补单线程的限制
异步任务
异步任务分为宏任务和微任务
- 宏任务:script ,setTimeout,setInterval,setI,setImmediate,I/O,UI-rendering
- 微任务:promise.then(),MutaionObserver,process.nexTick()
Event-Loop(事件循环)
JS中事件循环的流程:
- 执行同步代码 (这属于宏任务)
- 当执行栈为空,查询是否有异步需要执行
- 执行微任务
- 如果有需要,会渲染页面
- 执行宏任务 (下一次event-loop的开始)
举个例子:
这段代码的最终会输出什么呢?
console.log(1);
setTimeout(() => {
console.log(2);
new Promise((resolve) => {
console.log(4);
resolve()
setTimeout(() => {
console.log(6);
})
}).then(() => {
console.log(5);
})
}, 1000)
console.log(3);
- 第1行代码执行,此时输出 1。
- 然后是一个setTimeout函数,该函数存在一个1000毫秒后执行的回调函数。但是这个回调不会立即执行,而是被放入事件队列中,等待当前执行栈清空并且指定的延迟时间过去之后才执行。
- 第16行代码执行,输出 3。
- 等待1000毫秒,然后开始执行setTimeout的回调函数。
- 在这个回调函数里,先执行第4行代码,输出 2。
- 创建 Promise 对象。在这个 Promise 的执行函数中,执行 console.log(4),输出 4,然后执行到第6行的resolve()此时表示这个Promise已成功完成。
- 在Promise的执行函数中,第8行有个setTimeout,它的回调是一个异步操作,所以这个回调会被放入事件队列中,等待当前执行栈清空才执行。
- 因为 Promise 的 resolve() 已经被调用,.then() 中注册的回调函数将被放入微任务队列,等待当前执行栈清空后立即执行,这发生在下一个宏任务即第8行的setTimeout之前。
- 当前执行栈清空,事件循环首先检查微任务队列,发现 .then() 中的回调函数,执行后输出 5。
- 最后,事件循环处理下一个宏任务队列中的任务,执行之前安排在 Promise 执行函数中的 setTimeout 的回调函数,输出 6。