主线程运行的时候,产生堆和栈(堆heap栈stack)
栈中的代码调用各类外部API,他们在“任务队列”中加入各类事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取“任务队列”,依次执行那些事件所对应的回调函数。
执行栈中的代码(同步任务),老是在读取“任务队列”(异步任务)以前执行。
eg:浏览器
var req = new XMLHttpRequest(); req.open(''GET',url); req.onload = function(){}; req.onerror = function(){}; req.send();
上面代码中的req.send方法是Ajax操做向服务器发送数据,他是一个异步任务,意味着只有当前搅拌的全部代码执行完,系统才会去读取“任务队列”。因此,他与下面的写法等价。服务器
var req = new XMLHttpRequest(); req.open(); req.send(); req.onload = function(){}; req.onerror = function(){};
也就是说,指定回调函数的部分 onload和onerror,在send()方法的前面或者后面可有可无,由于他们属于执行栈的一部分,系统老是执行完他们,才会去读取“任务队列”。异步
定时器
出了放置异步任务的事件,“任务队列”还能够放置定时事件,即 指定某些代码在多少时间以后执行。这叫作“定时器”功能,也就是定时执行的代码。
定时器功能主要由setTimeout()和setInterval()这两个函数来完成,他们的内部运行机制彻底同样,区别在于前者指定的代码是一次性执行,后者则为反复执行。
setTimeout()接受两个参数,一个是回调参数,第二个是推迟执行的毫秒数。函数
console.log(1); setTimeout(function(console.log(2))}, 1000); console.log(3);
上面代码执行的结果是1,3,2。由于setTimeout()将第二行推迟到1000毫秒以后执行。
若是将setTimeout()第二个参数设为0,就表示当前代码执行完之后,也就是执行栈清空后,才会当即执行(0毫秒间隔)指定的回调函数。oop
setTimeout(function(){console.log(1);}, 0); console.log(2);
上面代码的执行结果是2,1。由于只有在执行完第二行之后,系统才会去执行“任务队列”中的回调函数,也就是setTimeout()。
总之,setTimeout(fn,o)的含义是,指定某个任务在主线程最先可得的空闲时间执行,也就是说,尽量早的执行。他在“任务队列”的尾部添加一个事件,所以要等到同步任务和“任务队列”现有的事件都处理完,才会获得执行。
HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,若是低于这个值,就会自动增长。在此以前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变更(尤为是涉及页面从新渲染的部分),一般不会当即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果汉语 setTimeout().url
须要注意的是,setTimeout()只是将事件插入了“任务队列”,必须等到当前代码执行完,(执行栈清空)。主线程才会去执行他指定的回调函数。要是当前代码耗时很长,有可能要等好久,因此并无办法保证,回调函数必定会在setTimeout()指定的时间执行。spa
Node.js的Event Loop
Node.js也是单线程的EventLoop,可是他的运行机制不一样于浏览器环境。线程
Node.js的运行机制以下code
1.V8引擎解析JavaScript脚本。
2.解析后的代码,调用NodeAPI。
3.libuv库复杂Node API的执行。他将不一样的任务分配给不一样的线程,造成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
4.V8引擎再将结果返回给用户。blog
除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与“任务队列”有关的方法:
process.nextTick和setImmediate。
他们能够帮助咱们加深对“任务队列”的理解。
process.nextTick方法能够在当前“执行栈”的尾部---下一次EventLoop(主线程读取“任务队列”)以前,触发回调函数。也就是说,他指定的任务老是发生在全部异步任务以前。
setImmediate方法则是在当前“任务队列”的尾部添加事件,也就是说,他指定的任务老是在下一次EventLoop时执行,这与setTimeout(fn,o)很像。
e.g
process.nextTick(function A(){ console.log(1); process.nextTick(function B(){ console.log(2); }) }); setTimeout(function timeout(){ console.log('timeout'); }, 0)
执行结果:1,2,timeout
上面代码中,因为process.nextTick方法指定的回调函数,老是在当前“执行栈”的尾部触发,因此不只函数A比setTimeout指定的回调函数timeout先执行,并且函数B也比timeout先执行。
这说明,若是有多个process.nextTick语句,无论他们是否嵌套,将所有在当前“执行栈”执行。
再看setImmediate
setImmediate(function A(){ console.log(1); setImmediate(function B(){ console.log(2); }); }); setTimeout(function timeout(){ console.log('timeout'); }, 0)
上面代码中,setImmediate与setTimeout(fn,o)各自添加了一个回调函数A和timeout,都是在下一次EvebtLoop触发。那么 运行的结果是不肯定的。
多是1,timeout,2也有多是timeout,1,2可是在Node.js文档中说,setImmediate指定的回调函数,老是排在setTimeout前面。