js single thread and event-loop
Pros and Cons for single threaded javasript
由浏览器决定,JavaScript主要用途是操作DOM,决定了它只能是单线程,否则会带来复杂的同步问题。
Pros:
- 适合高并发。
- 适合I/O密集型应用
Cons:
- 不适合CPU密集型应用。如果有长时间运行的计算(大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起。
大纲:
- 基本知识点,宏任务、微任务…
- 事件循环机制过程
浏览器中的事件循环
JavaScript代码执行过程中,除了依靠函数调用栈来搞定函数的执行顺序,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,成为事件循环过程。一个线程中,事件循环是唯一的,但任务队列可以拥有多个。任务队列又分为macro-task和micro-task,在最新标准中称为task和jobs。
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
macro-task大概包括:
- script(整体代码)
- setTimeOut
- setInterval
- setImmediate
- I/O
- UI render
- DOM
micro-task大概包括:
- process.nextTick
- Promise
- Async/Await(Promise)
- MutationObserver(h5新特性)
几个例子
1 | console.log('script start') |
依次输出为:
script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout
- 首先全局匿名函数最先执行,输出”script start”
- 然后遇到async1()函数,调用,将该函数压入执行栈,接着执行 “await async2()”。这里await关键字的作用就是await下面的代码只有当await后面的promise返回结果后才可以执行。而await async2() 语句就像执行普通函数一样执行async2(),进入async2输出”async2 end”。
- await关键字下面的语句相当于.then(),加入micro-task队列,那么async1函数执行结束,弹出执行栈。
- 遇到setTimeout(),加入macro-task队列。
- 接着执行Promise中的executor函数,然后.then()被加入micro-task队列。
- 然后执行最后的输出,当前宏任务结束。
- 此时事件循环机制开始工作:然后从micro-task队列中依次执行微任务。
而当await函数后面跟的是一个异步函数的调用(这里我们强调返回值为异步才为异步函数):
1 | console.log('script start') |
在最新的chrome v8引擎中执行为:
script start
async2 end
Promise
script end
async2 end1
promise1
promise2
async1 end
setTimeout
在这里我们理解为,进入await标记的代码后,将Promise后的首个链式调用注册为micro-task,然后继续执行,直到当前宏任务完成后,微任务也完成后,最后执行await后面的代码,最后再调用其它宏任务。
一个典型的async await的使用为:
1 | async function f() { |
我们可以将其简化理解为promise:
1 | function f() { |
『RESOLVE(p)』接近于『Promise.resolve(p)』,不过有微妙而重要的区别:p 如果本身已经是 Promise 实例,Promise.resolve 会直接返回 p 而不是产生一个新 promise。
这里我们复习一下Ajax原生请求:
1 | function search(term, onload, onerror) { |
如果使用Promise对象,可以写成如下:
1 | function search(term) { |