浏览器环境
以下两段代码是等价的。req对事件的回调设置,实际上就是当前主线程任务队列的任务。
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function (){};
req.onerror = function (){};
req.send(); //equal var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onload = function (){};
req.onerror = function (){};
setTimeout(fn,0):往消息队列尾部添加一条消息,指定主线程有空的时候(所有同步任务执行完),要尽快执行fn函数
对于事件循环应该这么理解:
主线程中有一个(同步)任务队列,另一个地方又有一个消息队列。
仅当任务队列为空时,主线程才会去检查消息队列,取消息,然后把对应的回调函数以一个任务的形式插入到主线程的任务队列中。
当任务队列不为空时,主线程会不停地从任务队列中取出任务,执行。直到任务队列为空,这个过程称为一次事件循环
事件循环实际上就是主线程【执行任务】和【取消息、插入任务】的过程,两部分不停循环
可见主线程所做的事情就是,执行任务、取消息、插入任务。那插入消息这个过程谁来做?
这里引入一个概念叫做“工作线程”,主线程执行任务时,可能会开启异步任务(如ajax,timeout等),这时实际上就是通知一个工作线程去执行这个异步任务,执行完成后,这个工作线程就往消息队列中插入一个消息,表示通知主线程,你交给我的任务我已经搞定啦
根据以上的理解可以发现,消息和回调函数必定是一一对应的。看看这个例子
var getMsg = new Promise(function(resolve,reject){
console.log('begin...');
resolve('ok');
});
getMsg.then(function(data){
console.log(data);
});
console.log('last');
运行输出:
begin...
last
ok
对于promise,看起来像触发回调的时候,这个回调还没定义。但实际上不是这样的,promise内置的回调会执行,然后这个内置的回调会调用我们自定义的回调方法而已,也就是说我们定义的回调方法是间接被调用的
node环境
node的事件循环不属于v8引擎的一部分,它由libuv库提供
process.nextTick:往主线程的同步任务队列尾部添加一个任务,这意味着这个任务执行在所有回调函数之前
setImmediate:和setTimeout(fn,0)类似
可见:
process.nextTick 和 setImmediate 相比,前者明显要高效与后者,因为前者运行在当前的事件循环中,不需要去检查消息队列