看了阮一峰老师的JavaScript 运行机制详解:再谈Event Loop和【朴灵评注】的文章,查阅网上相关资料,把本身对javascript运行模式和EVENT loop的理解整理下,不必定对,往后再看作个回顾。javascript
MDN上有张图很形象,
html
function f(b){
var a = 12;
return a+b+35;
}
function g(x){
var m = 4;
return f(m*x);
}
g(21);
上面函数调g用造成了一个 frames 的栈。调用g的时候,建立了第一个 frame,包含了 g 的参数和局部变量。当 g 调用 f 的时候,第二个 frame 就被建立、并置于第一个 frame 之上,包含了 f 的参数和局部变量。当f返回时,最上层的 frame 就出栈了(剩下 g 函数调用的 frame)。当g返回的时候,栈就空了。java
队列
一个 JavaScript 运行时包含了一个待处理的消息队列。每个消息都与一个函数相关联。当栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及所以而建立的一个初始栈结构)。当栈再次为空的时候,也就意味着消息处理结束。 node
在浏览器里,当一个事件出现且有一个事件监听器被绑定时,消息会被随时添加。若是没有事件监听器,事件会丢失。因此点击一个附带点击事件处理函数的元素会添加一个消息。其它事件亦然。ajax
毫不阻塞
一个颇有趣的事件循环 (event loop) 模型特性在于,Javascript 跟其它语言不一样,它永不阻塞。处理 I/O (input/output) 一般由事件或者回调函数进行实现。因此当一个应用正等待 IndexedDB 的查询的返回或者一个 XHR 的请求返回时,它仍然能够处理其它事情例如用户输入。浏览器
例外是存在的,如 alert 或者同步 XHR,但避免它们被认为是最佳实践。注意的是,例外的例外也是存在的(但一般是实现错误而非其它缘由)。markdown
Event Loop
举例node.js的Event Loop
异步
朴灵的解释函数
使用事件驱动的系统中,必然有很是很是多的事件。若是事件都产生,都要主循环去处理,必然会致使主线程繁忙。那对于应用层的代码而言,确定有不少不关心的事件(好比只关心点击事件,不关心定时器事件)。这会致使必定浪费。oop
【事实上,不是全部的事件都放置在一个队列里。】
【不一样的事件,放置在不一样的队列。】
【当咱们没有使用定时器时,则彻底不用关心定时器事件这个队列】
【当咱们进行定时器调用时,首先会设置一个定时器watcher。事件循环的过程当中,会去调用该 watcher,检查它的事件队列上是否产生事件(比对时间的方式)】
【当咱们进行磁盘IO的时候,则首先设置一个io watcher,磁盘IO完成后,会在该io watcher的事件队列上添加一个事件。事件循环的过程当中从该watcher上处理事件。处理完已有的事件后,处理下一个watcher】
【检查完全部watcher后,进入下一轮检查】
【对某类事件不关心时,则没有相关watcher】
定时器
定时器仅仅是在将来的某个时刻将代码添加到代码队列中,执行时机是不能保证的。代码队列按照先进先出的原则在主进程空闲后将队列中的代码交给主线程运行。
在Javascript中没有任何代码是马上执行的,带一旦进程空闲则尽快执行。例如,当某个按钮被按下时,事件处理函数会被添加到代码队列中。当接收到ajax响应时,回校函数的代码被添加到队列中。而定时器对队列的工做方式是,当特定的事件过去后将代码加入到队列中。设定一个150ms后执行的定时器不表明代码会在150ms以后执行,而是指代码会在150ms后加入到代码队列中。等到主进程空闲时而且该元素位于队列首位,其中的代码便会当即执行,看上去好像是在精确的时间点上执行了。实际上队列中的全部代码都要等到主进程空闲以后才能执行,而无论他们是怎额添加到队列中去的。
(function () { console.log('this is the start'); setTimeout(function cb() { console.log('this is a msg from call back'); }); console.log('this is just a message'); setTimeout(function cb1() { console.log('this is a msg from call back1'); }, 0); console.log('this is the end'); var time= new Date(); while(new Date() - time < 2000) {} })();
//打印顺序以下:
this is the start
this is just a message
this is the end
//2S以后执行settimeout内容,虽然0秒后执行setTimeout内容,可是主线程代码还没执行完,so等主线程空闲的时候再当即执行setTimeout代码
this is a msg from call back
this is a msg from call back1
当使用setInterval()时,仅当没有该定时器的任何其余代码实例时,才能将定时器代码添加到代码队列中。
var original = Date.now();
setInterval(function(){
console.log('run interval', Date.now() - original);
var start = Date.now();
while(Date.now() - start < 350) {};
original = Date.now();
}, 200);
var start = Date.now();
while(Date.now() - start < 300) {};
在使用setInterval时:
因此在使用setInterval作动画时要注意两个问题:
不能使用固定步长做为作动画,必定要使用百分比: 开始值 + (目标值 - 开始值) * (Date.now() - 开始时间)/ 时间区间
若是主进程运行时间过长,会出现跳帧的现象。为了不setInterval的两个缺点,可使用链式setTimeout():
setTimeout(function(){
//其余处理
setTimeout(arguments.callee, interval);
}, interval);