event-loop
javascript:
- 同步任务(主线程):存放于执行栈
- 异步任务:任务队列中(更确切的说是回调队列,queue,先进先出,但是有settimeout的特例)
主线程中执行会产生堆、栈,也同时会调用外部的api,会不断往任务队列中加入事件(click,mouseover…)。一旦执行栈中任务结束,就读取任务队列中,如果发现任务队列中某个异步事件有结果了(进入回调了),就拖到主线程的执行栈中执行。
event-loop是指:程序从主线程执行->调用外部api,在任务队列中放置事件->继续执行主线程->主线程清空/异步任务进入回调,执行任务队列中。(更新,更确切的说是,将task queue里面的函数提升到stack中去执行的这个事件一个一个循环的步骤)
======更新
之前看阮大大的博客,后来被推荐看jsConf上的个视频
讲解的很清楚,这边总结来说是:setTimeout(fn,delay)
:
webapis将事件隔delay时间后插入task queue,即使将delay设为0,也只是表示立刻将fn插入task queue(尾部),但是什么时候执行,时间不定,要等当前代码执行完(执行栈清空)以后,即eventloop需要等到stack清空以后再push task(callback)queue中的fn到stack中去执行
对于ajax这些,是等数据返回后,再把回调放入queue中。
render queue跟callback queue一样,只不过有更高的优先级,当stack清空完以后,会首先执行它,
因此如果是同步操作的话,会一直阻塞,让stack一直执行,render queue无法被call到,因此推荐异步,可以在执行间隙,每隔16ms(就会去访问下render queue,把它提上来(前提是stack清空))。
=======更新 end
Nodejs中的event-loop
添加了两个函数process.nextTick
和setImmediate
process.nextTick
: 始终在当前执行栈的尾部添加(如果有多个process.nextTick语句,不管它们是否嵌套,将全部在当前”执行栈”执行。)setImmediate
:始终在当前”任务队列”的尾部添加事件,如果嵌套,则下一轮,再下一轮..setTimeout
: 指定时间的话,是从当前执行开始计时,到时间就插入到task queue中,在任务队列的尾部添加。
关于setTimeout与setImmediate的执行顺序不定:
(官方文档中说是在非I/O context下,两者执行顺序不定,由进程的性能决定,但是在I/o环境中,比如fs的回调中,setImmedite始终在setTimeout之前决定)但是:
Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。
|
|
结论:
>我们由此得到了process.nextTick和setImmediate的一个重要区别:
多个process.nextTick语句总是在当前"执行栈"一次执行完,
多个setImmediate可能则需要多次loop才能执行完。
因此,当递归调用process.nextTick的话,会被⚠️要求用immediate替代,因为这样会一直在执行栈中,不会去检查任务队列。
补充知识
楼主看了下node里面介绍,关于process.nextTick,因为上面说它有一堆问题(什么递归调用会导致线程阻塞啊blahblah,那干嘛还要有这个api)。主要的原因是,比如在进入event-loop之前去做一些其他操作(有点点像finally),比如throw error以后,
|
|
要执行error回调之前(event-loop)想执行其他操作,比如一些unneeded资源的回收,或者请求重传等等。
还有的情况是诸如保证变量/函数定义、执行的先后顺序,
看下面一个例子:
|
|
用new执行完MyEmitter构造函数后,按理说要emit,但是这个时候其实并没有执行到下面(下面的on事件的注册也在执行栈中,不过还没调用到),因此emit要触发时还未定义。为了解决这个问题,应该将1处更改为2,那么,emit的触发会先暂缓,下面继续执行,event被注册,3处的回调被放入callback queue,然后这一步执行完后(也就是当前栈中的所有都已经执行完,只剩下刚刚nextTick中的emit),此时,事件已注册,因此触发,栈清空,event-loop开始,回调被挪入stack中执行。
课后小测试
|
|
答案: