JavaScript 运行原理解析( 二 )


The%20browser%20is%20then%20set%20up%20to%20listen%20for%20the%20response%20from%20the%20network,%20and%20when%20it%20has%20something%20to%20return%20to%20you,%20it%20will%20schedule%20the%20callback%20function%20to%20be%20executed%20by%20inserting%20it%20into%20the%20event%20loop.
上面这两段话摘自于How%20JavaScript%20works,以通俗的方式解释了JS如何调用回调函数实现异步处理 。
所以什么是Event%20Loop?
Event%20Loop只做一件事情,负责监听Call%20Stack和Callback%20Queue 。当Call%20Stack里面的调用栈运行完变成空了,Event%20Loop就把Callback%20Queue里面的第一条事件(其实就是回调函数)放到调用栈中并执行它,后续不断循环执行这个操作 。
一个setTimeout的例子以及对应的Event%20Loop动态图:
【JavaScript 运行原理解析】console.log('Hi');setTimeout(function%20cb1()%20{%20%20console.log('cb1');},%205000);console.log('Bye');复制代码

JavaScript 运行原理解析

文章插图
 
setTimeout有个要注意的地方,如上述例子延迟5s执行,不是严格意义上的5s,正确来说是至少5s以后会执行 。因为Web API会设定一个5s的定时器,时间到期后将回调函数加到队列中,此时该回调函数还不一定会马上运行,因为队列中可能还有之前加入的其他回调函数,而且还必须等到Call Stack空了之后才会从队列中取一个回调执行 。
所以常见的setTimeout(callback, 0) 的做法就是为了在常规的调用介绍后马上运行回调函数 。
console.log('Hi');setTimeout(function() { console.log('callback');}, 0);console.log('Bye');// 输出// Hi// Bye// callback复制代码在说一个容易犯错的栗子:
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000 * i);} // 输出:5 5 5 5 5复制代码上面这个栗子并不是输出0,1,2,3,4,第一反应觉得应该是这样 。但梳理了JS的时间循环后,应该很容易明白 。
调用栈先执行 for(var i = 0; i < 5; i++) {...}方法,里面的定时器会到时间后会直接把回调函数放到事件队列中,等for循环执行完在依次取出放进调用栈 。当for循环执行完时,i的值已经变成5,所以最后输出全都是5 。
关于定时器又可以看看这篇有意思的文章
最后关于Event Loop,可以参考下这个视频 。到目前为止说的event loop是前端浏览器中的event loop,关于Nodejs的Event Loop的细节阐述,请看我的另一篇文章Node.js design pattern : Reactor (Event Loop) 。两者的区别对比可查看这篇文章你不知道的Event Loop,对两种event loop做了相关总结和比较 。
总结
最后总结一下,JS的运行原理主要有以下几个方面:
  • JS引擎主要负责把JS代码转为机器能执行的机器码,而JS代码中调用的一些WEB API则由其运行环境提供,这里指的是浏览器 。
  • JS是单线程运行,每次都从调用栈出取出代码进行调用 。如果当前代码非常耗时,则会阻塞当前线程导致浏览器卡顿 。
  • 回调函数是通过加入到事件队列中,等待Event Loop拿出并放到调用栈中进行调用 。只有Event Loop监听到调用栈为空时,才会从事件队列中从队头拿出回调函数放进调用栈里 。




推荐阅读