事件循环
常规的 JavaScript 引擎是单线程的, 也就是说所有的代码块都是顺序按照顺序被执行, 这就导致遇到处理慢的代码块会阻塞软件的运行, 甚至使程序停止响应, 通常解决方案是使用异步来处理, 把需要时间处理的代码块异步的进行处理, 后面的块则继续执行, 等到该代码块处理完以后再回过头来进行结果的处理, 但是在 ES6 前并没有很好的异步解决方案, 所以大部分是使用 setTimeout 来进行处理. 下面就通过 setTimeout 的执行原理来理解一下 JavaScript 的事件循环机制.
首先看一下 JavaScript 的运行时模型:
JavaScript 引擎部分(例如 V8 引擎) , 黑框中部分
webAPIs 部分, 由宿主环境提供的额外 API 不属于引擎的原生部分
EventLoop & CallbackQueue 事件循环和回调队列, 同样属于宿主环境提供的机制, 用于辅助引擎工作
下面基于这个模型, 通过定时器来理解一下, JavaScript 的事件循环机制以及异步是如何调用的.
首先写一个基本的定时器
[图片上传失败...(image-5c33a6-1553756162784)]
1. 代码运行, 此时进行代码的解析
2. 调用 console.log('HI') 进入到调用栈中
3. 控制台打印 Hi
4. 解析下一部分代码
5. 执行定时器, 加入到调用栈中
6. 在 WebAPIs 中创建一个 Timer, 并将定时器的内容移过去
7. 定时器部分执行完毕, 弹出调用栈, 此时定时器内的内容被保存在 WebAPIs 环境当中
8. 调用 console.log('Bye') 进入到调用栈中
9. 控制台打印 Bye
10.console.log('Bye')弹出调用栈
11. 等待 WebAPIs 中的 timer 执行, 将 cb1 加入到回调队列中
12. 通过事件循环将回调队列中的 cb1 重新压入到调用栈中
13.cb1 内调用了 console.log('cb1')所以也要压入到调用栈中
14. 控制台打印 cb1
15. 弹出 console.log('cb1')
16. 弹出 cb1
image.PNG
通过对 setTimeout 的流程解析, 很容易发现 JavaScript 在运行时的调用过程是首先由 JS 引擎将代码解析编译, 然后根据调用顺序加入到调用栈中 (栈中的每一项都叫做帧) 逐帧执行, 其中需要用到 WebAPIs, 事件循环, 回调队列的辅助, 最后将执行的结果返回给调用处, 至此 JavaScript 就完成了一次调用的循环.
基础异步实现
上面的例子已经使用 setTimeout 实现了一个基础的异步调用但是需要注意的是, 虽然例子中使用的 setTimeout(myCallback, 5000); 但这并不意味着回调函数会在 5 秒后立即被执行, 而是表示回调方法在 5 秒后把回调函数添加到回调队列中, 如果此时队列中存在待处理任务, 那么该回调函数也会相应的被延迟执行.
所以即使是像下面这个例子一样也依然会是一个异步的调用结果, 因为 setTimeout 的第二个参数仅仅是延迟多久将回调内容放置到回调队列中, 而不是确保延迟多久后一定执行.
- console.log('Hi');
- setTimeout(function() {
- console.log('callback');
- }, 0);
- console.log('Bye');
- /** 输出结果 **/
- //Hi
- //Bye
- //callback
来源: http://www.jianshu.com/p/baf46c9b524b