引言
最开始学习JS的时候就从知道了JS是单线程的,天生异步,适合IO密集型,不适合CPU密集型。但是,多数初学者从来没有认真思考过自己程序中的异步到底是怎么出现的,以及为什么会出现,也没有探索过处理异步的其他方法,甚至于一直在用callback来解决异步问题。
为什么会出现异步
浏览器内核的多线程
一个浏览器至少三个常驻线程:JavaScript引擎线程,浏览器GUI渲染线程,浏览器事件触发线程。
- JS引擎,是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。
- GUI线程,当界面需要重绘或由于某种操作引发回流时,该线程就会执行。而因为JS可以操作DOM元素,进而会影响到GUI的渲染结果,因此JS引擎线程与GUI渲染线程是互斥的,也就是说当JS引擎线程处于运行状态时,GUI渲染线程将处于冻结状态。
- 浏览器事件触发线程,当一个事件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理,这些事件可来自JavaScript引擎当前执行的代码块,setTimeout, 也可以来自浏览器内核的其他线程如鼠标点击,AJAX异步请求等,但由于JS的单线程关系,所有这些事件都得排队等待JS引擎处理。
事件循环机制
任务队列,也就是事件队列,分为 宏任务(macro-task) 和 微任务(micro-task)
循环机制如下:
- 先顺序从上向下执行当前全局上下文
- 遇到异步事件就将其交给对应的浏览器模块
- 浏览器的模块处理完之后,宏任务放入宏任务队列,微任务放入微任务队列
- 当函数调用栈清空,开始执行任务队列,先执行微任务队列,执行完微任务队列再执行宏任务队列
- 当执行任务队列时,可以认为重新开了一个空的宏任务队列和一个空的微任务队列,将新产生的异步任务最终放入新的任务队列,当前任务队列执行完成后,当前宏队列和微队列就清除,然后再去执行新的微任务队列,执行新的宏任务队列,新开微队列,新开宏队列,一直循环下去,直到任务队列全部为空。
异步的处理方法
callback是最简单的,但不是最好的
//callback的一般使用形式$.ajax({ url:'www.pidanChen.com', success(data){ console.log(data) }})//延时处理try{ setTimeout(function(){ console.log('timeout') },500)}catch(e){ console.log('timerErr'+e)}
1. 如果某个业务,依赖于上层业务的数据,上层业务又依赖于更上一层的数据,还采用回调的方式来处理异步的话,就会出现回调地狱,也就是顺序问题。