【JS】Event Loop 中异步任务的疑问◔ ‸◔?
这两天看了 Event Loop 相关的技术文章,写了一个测试代码,发现测试结果并不稳定,可能会因为一些变量而导致不同的结果。不知道是浏览器实现的问题还是测试代码的问题。
参考技术文章
为了后面方便引用,给参考文章加上编号
JavaScript 运行机制详解:再谈Event Loop - $RYF
并发模型与Event Loop - MDN - $MDN
首先贴代码:
<!DOCTYPE html><html lang="zh">
<head>
<title>Event Loop Test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<script>
setTimeout(function() {
console.log('setTimeout延迟完成,执行回调:',Date.now());
}, 10);
console.log('setTimeout方法调用完成:',Date.now());
var req = new XMLHttpRequest();
req.open('GET', 'http://cdn.bootcss.com/jquery/1.11.3/jquery.min.js');
req.onload = function () {
console.log('XHR请求完成,执行回调:',Date.now());
};
req.send();
console.log('XHR请求发出:',Date.now());
console.time('同步任务延迟完成时间:');
var arr = [],max = 3000000;
for (var i = 1; arr.push(i++) < max;);
var mapArr = [];
arr.map(function (val) {
mapArr.push(val);
})
console.timeEnd('同步任务延迟完成时间:');
console.log('第一个<script>标签,同步任务结束时间:',Date.now());
</script>
<script>
console.log('另外一个<script>标签:',Date.now());
</script>
</body>
</html>
测试环境
Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3041.0 Safari/537.36
运行了很多次,把比较典型的三种结果贴出来:
测试结果一: XHR加载jQuery用时503mssetTimeout方法调用完成: 1489654050312
javascript-temp.html:28 XHR请求发出: 1489654050833
javascript-temp.html:37 同步任务延迟完成时间:: 2043.388916015625ms
javascript-temp.html:38 第一个<script>标签,同步任务结束时间: 1489654052877
javascript-temp.html:25 XHR请求完成,执行回调: 1489654052881
javascript-temp.html:42 另外一个<script>标签: 1489654052882
javascript-temp.html:18 setTimeout延迟完成,执行回调: 1489654054058
测试结果二: XHR加载jQuery用时1.36s
setTimeout方法调用完成: 1489664224372
javascript-temp.html:27 XHR请求发出: 1489664224375
javascript-temp.html:36 同步任务延迟完成时间:: 1480.636962890625ms
javascript-temp.html:37 第一个<script>标签,同步任务结束时间: 1489664225856
javascript-temp.html:17 setTimeout延迟完成,执行回调: 1489664225856
javascript-temp.html:24 XHR请求完成,执行回调: 1489664225861
javascript-temp.html:41 另外一个<script>标签: 1489664225862
测试结果三: XHR加载jQuery用时3.94s
setTimeout方法调用完成: 1489653980367
javascript-temp.html:28 XHR请求发出: 1489653980382
javascript-temp.html:37 同步任务延迟完成时间:: 1856.004150390625ms
javascript-temp.html:38 第一个<script>标签,同步任务结束时间: 1489653982239
javascript-temp.html:42 另外一个<script>标签: 1489653982243
javascript-temp.html:18 setTimeout延迟完成,执行回调: 1489653982330
javascript-temp.html:25 XHR请求完成,执行回调: 1489653984326
我的具体问题:
第二个
<script>
标签中的同步代码的执行结果为什么排在异步回调之后?$RYF 文章中第四章讲到:
执行栈中的代码(同步任务),总是在读取"任务队列"(异步任务)之前执行。
setTimeout()
的回调函数为什么会在主进程(另外一个<script>
标签中的同步代码)和异步任务(XHR
请求 jQuery 文件)之前调用?$RYF 文章中第五章讲到:
setTimeout(fn,0)
的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。$MDN 中的 事件循环 -> 添加消息 段落讲到:
调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。
难道测试结果二和三种,
setTimeout()
的回调函数出现在XHR
的回调函数之前是因为当时消息队列中为空吗?
往消息队列中添加消息是在事件触发的时候嘛?
例如,通过
XHR
请求 jQuery 资源,待 jQuery 文件加载完成会触发“加载完成事件”,这时候会在消息队列中添加一个消息,等待主进程读取并调用对应的回调函数。
为什么 jQuery 资源加载时间的变化,会引起另外一个
<script>
标签中的同步代码、XHR
的回调以及setTimeout()
方法的回调,这三段代码执行先后的变化?
如有描述错误地方,请指正。。
--------------------2017/3/17分割线--------------------------
为各位补充一篇我今天找到的关于浏览器工作原理的文章:
浏览器的工作原理:新式网络浏览器幕后揭秘
如果连接无法打开,可以访问这个备用的:
印象笔记
回答
说下我的理解吧,先明确几个基本概念
js引擎
只负责实现ecmascript
标准,按照标准执行代码,它不关心也不知道event loop" title="event loop">event loop
的存在,你给它什么代码它就执行什么event loop
是js运行环境
内部使用的机制,运行环境也就是Node
以及各种各样的浏览器
js引擎
执行代码时会有一个执行栈
,执行栈中的代码执行完毕后,浏览器(JS运行环境)
才会从event queue
中取出事件,如果该事件有对应的callback
,则再次交给js引擎
执行。所有的事件在产生后都会被浏览器放到
event queue
中,事件可以来源于鼠标键盘、网络IO、定时器等。event queue
一般是由多个不同优先级
的队列组成,分别对应不同类型的事件,具体的细节由实现者自己决定。
现在,根据你描述的问题,我尝试还原一下整个过程。
浏览器取得
HTML
文件后,做的第一件事就是解析HTML
(它内部有另外的模块来做这件事),构建DOMTree
,当遇到script
标签,会停止解析,交给js引擎
执行标签内的javascript
代码块,这就是我们通常说的js
会阻塞页面渲染。 此时js引擎
执行栈中只有第一个script
标签内的代码。一旦这段代码执行完毕,浏览器会检查event queue
,这个时候,根据event queue的情况以及浏览器自身的实现策略就可能会有不同的结果。浏览器可能会按照先后顺序,或者它预定的优先级依次取出全部事件给js引擎执行
callback
,还有可能为了加快页面渲染速度,只取出部分高优先级事件。最后浏览器继续解析页面,遇到下一个
script
标签再次交给js引擎
执行代码
我在Chrome57下多次执行了你的代码,只会出现第1,3种情况
这很好理解,当第一段script
执行完时,chrome
取出事件时,忽略低优先级的timeout
事件,如果已经有xhr
事件则取出,没有就继续解析HTML
,碰到第二个script
再次用js引擎
执行。
最后,已上纯属个人见解。有些概念其实依赖于具体的实现的,不同浏览器的差异可能会导致表现出来的行为就不一样,要深入的了解细节只能去看它们的源码了。。。
先贴个有相关性的问题链接
1.第一个问题还是比较好理解的,很可能是在第二个script还未被解析(先解析后执行)的时候,第一个script中的异步回调被触发了。此时执行栈是空的。
2.前一半同问题一,后一半,异步回调的执行顺序跟建立的顺序无关,谁先触发,谁就在前面。
3.我觉得是对的。
4.等别的回答。
也得纠正上面的错误。
这是一个蛮有意思的问题,但追根到底其实就是一个事件驱动的问题。
对于浏览器而言,setTimeout
消息和 XMLHttpRequest
AJAX消息会被当作两种不同的消息放在消息队列里面。
消息,就会有众多因素而倒置输出的顺序不可预见!
既然如此,那么如果我们把两个消息拿掉,那么你的三个执行顺序全是正常的,这样第一个问题就迎刃而解。
以下来自MDN的一张Event Loop的图式:
其中 Queue
由浏览器维护,换句话说队列中的消息是由浏览器来放入的。
但这里有一个前置条件,就是不管是哪种消息,都遵循一种CPU TICK的时间限定,大概就是4ms(如果使用的是电池且量底的情况下可能会更长)。
因此,不知道题主有没有注意到一个细节,即两个消息的时间不可能会存在相隔1ms的情况,即:
javascript-temp.html:17 setTimeout延迟完成,执行回调: 1489664225856javascript-temp.html:24 XHR请求完成,执行回调: 1489664225861
// 二者的时间不会存在 1489664225856 和 1489664225857
而对于Event Loop中的消息处理是不存在这种因素:
javascript-temp.html:25 XHR请求完成,执行回调: 1489654052881javascript-temp.html:42 另外一个<script>标签: 1489654052882
// 当一个消息放进后,主线程的立即开始
不管是什么的消息,都是依赖于浏览器的事件驱动,那么这一话题就会伸到不同浏览器之间的差异。假如你把测试代码放在 Chrome、IE、Safari 等浏览器下,所执行的结果也和 Firefox 不同。更甚至,可能你把 <script>
由 <body>
迁移到 <head>
里面,也可能会引发不同的结果。
说了,那么多,我也没办法提供一个准确的答案!
照理说 不应该出现结果2 的情况才对
题主你理解这个eventloop机制了没?我最近也看到关于eventloop的资料,有点不明白事件队列里面有没有优先级之分,或者说浏览器在将异步任务push进任务队列有没有优先级的,想ajax、dom事件、setTimeout他们有没有什么先后顺序
以上是 【JS】Event Loop 中异步任务的疑问◔ ‸◔? 的全部内容, 来源链接: utcz.com/a/83433.html