事件循环学习2
主线程运行的时候,产生堆和栈(堆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毫秒间隔)指定的回调函数。
setTimeout(function(){console.log(1);}, 0); console.log(2);
上面代码的执行结果是2,1。因为只有在执行完第二行以后,系统才会去执行“任务队列”中的回调函数,也就是setTimeout()。
总之,setTimeout(fn,o)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早的执行。他在“任务队列”的尾部添加一个事件,因此要等到同步任务和“任务队列”现有的事件都处理完,才会得到执行。
HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果汉语
setTimeout().
需要注意的是,setTimeout()只是将事件插入了“任务队列”,必须等到当前代码执行完,(执行栈清空)。主线程才会去执行他指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
Node.js的Event Loop
Node.js也是单线程的EventLoop,但是他的运行机制不同于浏览器环境。
Node.js的运行机制如下
1.V8引擎解析JavaScript脚本。
2.解析后的代码,调用NodeAPI。
3.libuv库复杂Node API的执行。他将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
4.V8引擎再将结果返回给用户。
除了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前面。