前言
JS 异步执行机制具有非常重要的地位, 尤其体现在回调函数和事件等方面. 本文将针对 JS 异步执行机制进行一个简单的分析.
从一份代码讲起
下面是两个经典的 JS 定时执行函数, 这两个函数的区别相信对 JS 有一定基础的同学是十分清楚的. timeout 仅仅只会执行一次, 而 interval 则会执行多次.
- setTimeout(function (args) {
- console.log('timeout')
- }, 1000);
- setInterval(function (args) {
- console.log('interval')
- }, 1000);
那么再看一份代码
- setTimeout(function (args) {
- console.log('timeout');
- setTimeout(arguments.callee, 1000);
- }, 1000);
- setInterval(function (args) {
- console.log('interval')
- }, 1000);
这两份代码是否存在区别呢? 在 setTimeout 中递归调用貌似和 setInterval 一样, 但是实际上由于 JS 异步执行机制的问题, 导致这两个函数存在着一定的差异.
如何理解 JS 异步执行机制
JS 是单线程程序, 从而避免了并发访问的一系列问题. 但也正是由于单线程这样一个机制, 导致 JS 的异步执行并不能按照传统的多线程方式进行异步执行, 所有的异步时间要插入到同一个队列中, 依次在主线程中执行.
这里有一张图片, 可以比较好的解释 JS 的异步执行机制.
在浏览器中, 一般情况下会存在三个线程, JS 执行引擎, HTTP 线程, 事件触发线程. 但是需要注意的是, 所有的 JS 核心逻辑都需要在 JS 执行引擎线程中执行.
例如我们可以使用下面这样一段代码发送 AJAX 请求
- var xmlReq = createXMLHTTP();// 创建一个 xmlhttprequest 对象
- function testAsynRequest() {
- var url = "http://127.0.0.1:5000/";
- xmlReq.open("post", url, true);
- xmlReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- xmlReq.onreadystatechange = function () {
- if (xmlReq.readyState == 4) {
- if (xmlReq.status == 200) {
- var jsonData = eval('(' + xmlReq.responseText + ')');
- alert(jsonData.message);
- }
- else if (xmlReq.status == 404) {
- alert("Requested URL is not found.");
- } else if (xmlReq.status == 403) {
- alert("Access denied.");
- } else {
- alert("status is" + xmlReq.status);
- }
- }
- };
- xmlReq.send(null);
- }
- testAsynRequest();//1 秒后调用回调函数
- while (true) {
- }
服务端代码
- from flask import Flask
- app = Flask(__name__)
- @app.route('/', methods=['POST', 'GET'])
- def print():
- return 'hello world'
- if __name__ == '__main__':
- app.run()
这段代码是否会输出 hello world 呢? 经过测验, 发现并不会输出 HelloWorld, 浏览器会进入假死状态. 造成这种情况的原因正是 JS 异步回调单线程的运行机制. 在发送 HTTP 请求以后, HTTP 请求会启动一个线程进行发送, 收到响应以后会, 事件触发线程会将响应事件加入到等待队列中, 等待 JS 引擎空闲后执行.
但是由于 while(true) 导致 JS 引擎永远不存在空闲, 从而导致响应事件一致无法触发.
重新思考
通过一个简单的 AJAX DEMO, 可以简单了解了 JS 时间执行的一个流程. 那么针对上面的那张图片, 和最开始提出的 settimeout 的问题, JS 又是如何调度和处理的呢?
JS 在定时器函数初始化以后就会开始执行定时任务, 到达时间之后如果此时 JS 引擎空闲, 则会直接执行定时任务, 否则会将定时任务加入到等待队列中.
对于加入到等待队列中的任务来说, 会在 JS 引擎空闲的时候再不断进行执行. 因此如果此时引擎并非空闲, 那么 setTimeout 会等待一段时间后才能执行.
对于 setInterval 来说, 也是需要加入到等待队列中的, 但是 setInterval 并不会因为加入到等待队列中而停止计时, 此时如果到了第二个 Interval, 而第一个 Interval 还没有开始执行, 那么此时队列中旧有存在两个 Interval 可能, 如果这样累加下去, 那么就可能会陷入大量 Interval 的累加, 造成线程严重阻塞的问题, 因此 JS 引擎做了一个轻度的优化, 如果队列中有 Interval, 那么这个 Interval 不会加入队列. 但是如果 Interval 已经 pop 出队列开始执行, 那么 Interval 将会加入队列.
针对上面的分析, 我们可以得出一个结论, 相比于 setTimeout 函数递归调用, 在 JS 中由于单线程的异步执行机制, setInterval 执行的频率会更高. 因为 setTimeout 在执行完成以后才会开始下一轮定时任务, 但是 setInterval 是持续执行定时任务, 尤其是在 setTimeout 里的任务执行时间较长的时候, setInterval 和 setTimeout 会有比较明显的频率差异.
来源: https://www.cnblogs.com/zhenlingcn/p/8971081.html