记录自视频What the heck is the event loop anyway? – Philip Roberts
js是一个单线程语言,只有一个调用栈(call stack),在同一时间只能做一件事情。
one thread == one call stack == one thing at a time
当调用一个函数的时候,我们把这个函数的执行上下文push到调用栈中,当函数执行完毕再将它pop出来。
虽然js在运行时是one time one thing,但是浏览器除了js引擎之外还提供了其他东西,比如一些API,这是一些可以调用的其他线程。比如处理定时器的、ajax的等接口等。
当我们使用了setTimeout,对应的webapi会进行计时,在计时完毕后将回调函数push到任务队列中去。
event loop所做的事情就是观察stack和任务队列。
当stack为空时,就将任务队列中的第一个东西取出并push到stack中。
因此像setTimeout(func,0)
这种用法,就是为了推迟func的执行。虽然setTimeout(func,0)使得func马上就被加入到了任务队列中,但是还是要等到stack为空后event loop才能去取任务队列中的func到stack中然后执行。
所有那些webapi都是相同的工作方式,如ajax请求、事件处理。
我们总说UI渲染和js执行是一个线程,具体是什么意思呢?
相对于任务队列来说,还有一个负责render的队列为render队列,就是渲染队列。和任务队列一样,渲染队列也只是在stack为空时才会被event loop调用,并且渲染队列的优先级比任务队列高。
我们知道,一般浏览器以16.6ms为一个周期进行render。也就是16.6ms 渲染队列中会出现渲染任务,当stack不为空时,这个渲染任务就被阻塞了。而当stack为空时,如果当前渲染队列中需要render,event loop就会取出push到stack中,若当前正好是render 的间隙,event loop可能就会去取任务队列中的任务到stack中执行。
event loop规范
HTML5规范里有Event loops这一章节(读起来比较晦涩,只关注相关部分即可)。
- 每个浏览器环境,至多有一个event loop。
- 一个event loop可以有1个或多个task queue。
- 一个task queue是一列有序的task,用来做以下工作:Events task,Parsing task, Callbacks task, Using a resource task, Reacting to DOM manipulation task等。
macrotack与microtask?
上面所说的task就是macrotask,
而microtask在ES2015规范中称为Job。
macrotask
- script
- setTimeout
- setInterval
- setImmediate
- I/O
而microtask包括:
- process.nextTick
- promise
- Object.observe
其实microtask就相当于一种插队操作,不同于macrotask每次都会添加到任务队列的队尾,会在下一个tick才执行,microtask则会在下一个tick之前执行。
因此针对如下代码:
Promise.resolve(42).then(console.log(1));
setTimeout(function(){console.log(2)},0)
最终的结果是1,2。因为Promise的then会在下一个tick前运行,而setTimeout则会在当前栈空了的情况下才被执行,也就是下一个tick才会执行,因此最终输出1,2。
还有一种解释就是,浏览器会先执行一个macrotask,之后取出microtask中的所有任务并顺序执行;之后再取macrotask的一个任务,如此周而复始……