目录
背景
Javascript的运行时环境
Javascript的运行时环境一般是指浏览器或者Node.js。 一般来说Javascript运行时环境除了用来解析和执行JS代码的Javascript引擎,还包括WebAPI,线程池,任务队列,EventLoop等等。这些组件组合起来后为Javascript提供一个异步响应式的环境。
同步与异步
同步:一次只能做一件事。Javacript引擎是一个单线程。负责解释和执行JavaScript代码这意味着一次只能做一件事。好比单行车道, 一次只能通过一辆车,故容易造成阻塞。
单线程只能做同步操作,那么如何解决阻塞带来的问题? 异步编程。Javascript运行时提供了一个EventLoop的事件循环模型,来处理异步操作。下面本人从EventLoop的结构和代码案例,两方面分析EventLoop工作原理。
EventLoop运行机制
概览:EventLoop在Javascript运行时(浏览器环境)的位置。
Call stack:函数调用栈。JS每次执行一行代码,
WebAPI:当调用栈遇到setTimeout(callback,timeout)API的时候,会交给浏览器的计时器倒计时,时间到达后,会把callback压入callBackQueue中。
CallbackQueue:callBackQueue用来保存回调函数
EventLoop:事件循环器周期性的检测调用栈是否为空,如果调用栈为空,则会把callbackQueue的回调出队,放入调用栈中执行。
RenderQueue: 渲染事件队列。EventLoop会优先调度渲染队列中的callback。
调用栈的代码是在JS单线程中执行的。所以callback和调用栈如果执行时间过长,会导致页面渲染的过程被阻塞住。
GUI进程中的GUI渲染线程负责页面渲染,但是其同JS的单线程是互斥的,两者通过EventLoop通信。
案例分析
function pipe(){
console.log('do easy sth... 1');
setTimeout(function (){
console.log("do hard sth... 2")
}, 1000)
setTimeout(function (){
console.log("do hard sth... 3")
}, 1000)
setTimeout(function (){
console.log("do hard sth... 4")
}, 1000)
setTimeout(function (){
console.log("do hard sth... 5")
}, 1000)
setTimeout(function (){
console.log("do hard sth... 6")
}, 1000)
console.log('do easy sth... 7')
}
//输出:1,7,2,3,4,5,6
上面代码输出为什么不是1,2,3,4,5,6,7? 这是因为,JS优先执行调用栈内的代码,所以1 ,7会先打印,其次setTimeout会把其callback放到Callback队列等待被EventLoop调度。而EventLoop只有等调用栈为空的时候,才会依次调度CallBack队列。所以我们要尽量保证UI渲染靠前执行,且调用栈不要做耗时操作。
延伸:宏任务和微任务
微任务:Promise每次调用then()时,注册的任务。
宏任务:callback队列或者调用栈中的每帧都是宏任务,宏任务执行完会立即执行当前微任务队列,之后才会切换到渲染线程执行页面渲染任务。
它们之间的关系: 宏任务->微任务->渲染任务-->宏任务...
总结
EventLoop是GUI编程中常见的异步编程的模型,Android开发中也有其身影。调用栈就像流水线,专门生产产品,遇到需要耗时操作的任务,就扔给产品打磨部门(WebAPI),产品打磨部门把耗时任务做完,任务结果,放入队列排队,交给EventLoop这个调度员调度,EventLoop监控流水线有空的时候就会轮训callback队列,把CallBack队列的耗时任务结果拿出来,交给流水线继续干活。