浅薄概念
Javascript是单线程,执行任务时,分同步任务和异步任务,执行同步任务时放入栈中执行,执行异步任务时由浏览器放入异步队列,等同步任务执行完后,再去异步队列询问是否有可执行的回调函数,一直循环直到异步队列清空。
没有深入!
深入理解
函数调用栈和任务队列
JavaScript有一个main thread主进程和call-stack(一个调用堆栈),在对
一个调用堆栈中的task处理的时候,其他的都要等着。当在执行过程中遇到一些类似于setTimeout等异步操作的时候,会交给浏览器的其他模块(以webkit为例,是webcore模块)进行处理,当到达setTimeout指定的延时执行的时间后,task(回调函数)会放入到任务队列直至,一般不同的异步任务的的回调函数会放入不同的任务队列之中。等到调用栈中所有task执行完毕之后,接着去执行任务队列中的task(回调函数)
在上图中,调用栈中遇到DOM操作、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理,webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现。等到这些模块处理完这些操作的时候将回调函数放入任务队列中,之后等栈中的task执行完之后再去执行任务队列之中的回调函数。
从setTimeout看事件循环机制
一个例子来说明事件循环机制究竟是怎么执行setTimeout的。
首先main()函数的执行上下文入栈,
代码接着执行,遇到console.log(‘Hi’),此时log(‘Hi’)入栈,console.log方法只是一个webkit内核支持的普通的方法,所以log(‘Hi’)方法立即被执行。此时输出’Hi’。
当遇到setTimeout的时候,执行引擎将其添加到栈中。
调用栈发现setTimeout是之前提到的WebAPIs中的API,因此将其出栈之后将延时执行的函数交给浏览器的timer模块进行处理。
timer模块去处理延时执行的函数,此时执行引擎接着执行将log(‘SJS’)添加到栈中,此时输出’SJS’。
当timer模块中延时方法规定的时间到了之后就将其放入到任务队列之中,此时调用栈中的task已经全部执行完毕。
调用栈中的task执行完毕之后,执行引擎会接着看执行任务队列中是否有需要执行的回调函数。这里的cb函数被执行引擎添加到调用栈中,接着执行里面的代码,输出’there’。等到执行结束之后再出栈。
小结
上面的这一个流程解释了当浏览器遇到setTimeout之后究竟是怎么执行的,相类似的还有前面图中提到的另外的API以及另外一些异步的操作。
总结上文说的,主要就是以下几点:
所有的代码都要通过函数调用栈中调用执行。
当遇到前文中提到的APIs的时候,会交给浏览器内核的其他模块进行处理。
任务队列中存放的是回调函数。
等到调用栈中的task执行完之后再回去执行任务队列之中的task。
问题
(function test() {
setTimeout(function() {console.log(4)}, 0);
new Promise(function executor(resolve) {
console.log(1);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(2);
}).then(function() {
console.log(5);
});
console.log(3);
})()
在这段代码里面,多了一个promise,那么我们可以思考下面这个问题:
promise的task会放在不同的任务队列里面,那么setTimeout的任务队列和promise的任务队列的执行顺序又是怎么的呢?
到这里大家看了我说了这么多的task,那么上文中一直提到的task究竟包括了什么?具体是怎么分的?
原文:https://zhuanlan.zhihu.com/p/26229293