定义:
JavaScript是基于单线程运行的,同时又是异步执行的,一般来说这种既是单线程又是异步的语言都是基于事件来驱动的,恰好浏览器给JavaScript提供了这种环境
执行过程
同步和异步任务分别进入不同的执行"场所"",同步的进入主线程,异步的进入Event Table并注册函数
当指定的事情完成之后,Event Table会将这个函数移入Event Queue
主线程的任务执行完毕为空,回去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的事件循环(Event Loop)
同步事件(先进后出heap)
- 代码没有执行的时候,执行栈为空栈
- foo函数执行时,创建一帧,这帧中包含了形参、局部变量(预编译过程),然后把这一帧压入栈中
- 然后执行foo中的代码,执行bar函数
- 创建新帧,同样有形参、局部变量,压入栈中
- bar函数执行完毕,弹出栈
- foo函数执行完毕,弹出栈
- 执行栈为空
异步事件(先进先出stack)
Ajax进入Event Table, 注册并回调函数success
执行console.log(‘run’)
ajax事件完成http网络请求线程把事件放到Event Queue中 主线程(调用栈)读取任务下执行success函数
浏览器常驻的线程
- js引擎线程(解释执行js代码、用户输入、网络请求)
- GUI(绘制用户界面,与js主线程是互斥的)
- http网络请求线程(处理用户的get、post等请求,等返回结果后将回调函数推入任务队列)
- 定时触发器线程(setTimeout、setInterval等待时间结束后把执行函数推入任务队列中)
- 浏览器事件处理线程(将click、mouse等交互事件发生后将这些事件放入事件队列中)
重新理解定时器
setTimeout的等待时间结束后并不是直接执行的而是先推入浏览器的一个任务队列,在同步队列结束后在依次调用任务队列中的任务。
setTimeout(function(){},0)js主线程的执行栈为空时,0ms实际上也达不到,根据HTML标准,最低4ms
setInterval是每隔一段时间把任务放到Event Queue之中
宏任务和微任务
宏任务 -> 微任务 -> GUI渲染 -> 宏任务 -> …
-
宏任务(macrotask)也称为task:我们可以将每次执行栈执行的代码当做是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行), 每一个宏任务会从头到尾执行完毕,不会执行其他
-
微任务(microtask) :当执行到script脚本的时候,js引擎会为全局创建一个执行上下文,在该执行上下文中维护了一个微任务队列,当遇到微任务,就会把微任务回调放在微队列中,当所有的js代码执行完毕,在退出全局上下文之前引擎会去检查该队列,有回调就执行,没有就退出执行上下文,这也就是为什么微任务要早于宏任务,也是大家常说的,每个宏任务都有一个微任务队列(由于定时器是浏览器的API,所以定时器是宏任务,在js中遇到定时器会也是放入到浏览器的队列中)
关于宏任务和微任务的定义参考了这篇文章:https://juejin.im/post/5e22b391f265da3e204d8c14#heading-24