1.进程与线程

       面试题:进程与线程区别?JS 单线程带来的好处?

  • 进程:描述了 CPU 在运行指令及加载和保存上下文所需的时间
  • 线程:是进程中的更小单位,描述了执行一段执行所需的时间

       当在浏览器打开一个 Tab 页时,就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。

       JS 单线程可以达到节省内存,节约上下文切换时间、没有锁的问题的好处。在 JS 运行时可能会阻止 UI 渲染,说明两个线程是互斥的,避免 JS 修改 DOM 的时候 UI 线程在工作就可能导致不能安全渲染 UI。

2.执行栈

       面试题:什么是执行栈?

       可以理解为是一个存储函数调用的栈结构,遵循先进后出的原则。

       当开始执行 JS 代码时,首先会执行一个 main 函数,然后执行我们的代码。根据先进后出的原则,后执行的函数会先弹出栈。

       当我们使用递归的时候,因为栈可存放的函数是有限制的,一旦存放了过多的函数且没有得到释放的话,就会出现栈溢出的问题
执行栈示意图

3.浏览器中的 Event Loop

       面试题:异步代码执行顺序?解释一下什么是 Event Loop ?

       执行 JS 代码的时候其实就是往执行栈中放入函数,遇到异步代码时,会被挂起并在需要的时候加入到 Task 队列中。执行栈为空,Event Loop 就会从 Task 队列拿出需要执行的代码并放入执行栈中执行。

  • 宏任务包括 :script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering
  • 微任务包括 :process.nextTick ,promise ,MutationObserver
           例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
console.log('script start') // 1

async function async1() {
await async2()
console.log('async1 end') // 5,相当于在Primise的then中
}
async function async2() {
console.log('async2 end') // 2,返回一个Promise
}
async1()

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

new Promise(resolve => {
console.log('Promise') // 3
resolve()
})
.then(function() {
console.log('promise1') // 6
})
.then(function() {
console.log('promise2') // 7
})

console.log('script end') // 4,同步执行完毕

所以 Event Loop 执行顺序如下所示:

  1. 首先执行同步代码,这属于宏任务
  2. 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
  3. 执行所有微任务
  4. 当执行完所有微任务后,如有必要会渲染页面
  5. 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数

复杂的Event Loop执行顺序示意