我们经常说JS是单线程的,比如Node.js研讨会上大家都说JS的特色之一是单线程的,这样使JS更简单明了,可是大家真的理解所谓JS的单线程机制吗?单线程时,基于事件的异步机制又该当如何,这些知识在《JavaScript权威指南》并没有介绍,我也一直困惑了,直到看到一篇外文,才有了些眉目,这里与大家分享下。翻译的过程中,发现已有人翻译了这篇文章,于是乎,在某些语句上,借鉴了下。文章网址:链接。后来发现《JavaScript高级程序设计》高级定时器和循环定时器介绍过,不过觉得没我翻译这篇原文介绍得更透彻,觉得我写的不好的,可以查看原外文

1         先看下两个例子

1.1.       简单的settimeout

setTimeout(function () { while (true) { } }, 1000);

setTimeout(function () { alert('end 2'); }, 2000);

setTimeout(function () { alert('end 1'); }, 100);

alert('end');

  执行的结果是弹出’end’、’end 1’,然后浏览器假死,就是不弹出‘end 2’。也就是说第一个settimeout里执行的时候是一个死循环,这个直接导致了理论上比它晚一秒执行的第二个settimeout里的函数被阻塞,这个和我们平时所理解的异步函数多线程互不干扰是不符的。

附计时器使用方法

--初始化一个简单的js的计时器,一段时间后,才触发并执行回调函数。 setTimeout 返回一个唯一id,可用这个id来取消这个计时器。

var id = setTimeout(fn,delay);

--类似于setTimeout,不一样的是,每隔一段时间,会持续调用回调fn,直到被取消

var id = setInterval(fn,delay);

--传入一个计时器的id,取消计时器。

clearInterval(id);

clearTimeout(id);

1.2.       ajax请求回调

接着我们来测试一下通过xmlhttprequest实现ajax异步请求调用,主要代码如下:

var xmlReq = createXMLHTTP();//创建一个xmlhttprequest对象

function testAsynRequest() {

var url = "/AsyncHandler.ashx?action=ajax";

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) {

}

在服务端实现简单的输出:

private void ProcessAjaxRequest(HttpContext context)

{

string action = context.Request["ajax"];

Thread.Sleep(1000);//等1秒

string jsonObject = "{\"message\":\"" + action + "\"}";

context.Response.Write(jsonObject);

}

  理论上,如果ajax异步请求,它的异步回调函数是在单独一个线程中,那么回调函数必然不被其他线程”阻挠“而顺利执行,也就是1秒后,它回调执行弹出‘ajax’,可是实际情况并非如此,回调函数无法执行,因为浏览器再次因为死循环假死。

  据上面两个例子,总结如下:

①    JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序.

②    JavaScript引擎用单线程运行也是有意义的,单线程不必理会线程同步这些复杂的问题,问题得到简化。

2.       JavaScript引擎

  可JS内部究竟如何实现,我们在接下来探讨。

  在了解计时器内部运作前,我们必须清楚一点,触发和执行并不是同一概念,计时器的回调函数一定会在指定delay的时间后被触发,但并不一定立即执行,可能需要等待。所有JavaScript代码是在一个线程里执行的,像鼠标点击和计时器之类的事件只有在JS单线程空闲时才执行。

JS 的线程、事件循环、任务队列简介

JS 是单线程的,但是却能执行异步任务,这主要是因为 JS 中存在事件循环(Event Loop)和任务队列(Task Queue)。

事件循环:JS 会创建一个类似于 while (true) 的循环,每执行一次循环体的过程称之为 Tick。每次 Tick 的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中,也就是每次 Tick 会查看任务队列中是否有需要执行的任务。

任务队列:异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含上图中的3种 webAPI,分别是 DOM Binding、network、timer模块。

  onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。

  setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。

  ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。

主线程:JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。

Update:

《你不知道的 JavaScript》一书中,重新讲解了 ES6 新增的任务队列,和上面的任务队列略有不同,上面的任务队列书中称为事件队列。

上面提到的任务(事件)队列是在事件循环中的,事件循环每一次 tick 便执行上面所述的任务(事件)队列中的一个任务。而任务(事件)队列是只能往尾部添加任务。

而 ES6 中新增的任务队列是在事件循环之上的,事件循环每次 tick 后会查看 ES6 的任务队列中是否有任务要执行,也就是 ES6 的任务队列比事件循环中的任务(事件)队列优先级更高。

如 Promise 就使用了 ES6 的任务队列特性。

  

3.       JavaScript引擎线程和其它侦听线程

   在浏览器中,JavaScript引擎是基于事件驱动的,这里的事件可看作是浏览器派给它的各种任务,这些任务可能源自当前执行的代码块,如调用setTimeout(),也可能来自浏览器内核,如onload()、onclick()、onmouseover()、setTimeOut()、setInterval()、Ajax等。如果从代码的角度来看,所谓的任务实体就是各种回调函数,由于“单线程”的原因,这些任务会进行排队,一个接着一个等待着被引擎处理。

上图中,定时器和事件都按时触发了,这表明JavaScript引擎的线程和计时器触发线程、事件触发线程是三个单独的线程,即使JavaScript引擎的线程被阻塞,其它两个触发线程都在运行。

  浏览器内核实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步。假如某一浏览器内核的实现至少有三个常驻线程: JavaScript引擎线程,事件触发线程,Http请求线程,下面通过一个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通信的。虽然每个浏览器内核实现细节不同,但这其中的调用原理都是大同小异。

线程间通信:JavaScript引擎执行当前的代码块,其它诸如setTimeout给JS引擎添加一个任务,也可来自浏览器内核的其它线程,如界面元素鼠标点击事件,定时触发器时间到达通知,异步请求状态变更通知等.从代码角度看来任务实体就是各种回调函数,JavaScript引擎一直等待着任务队列中任务的到来.由于单线程关系,这些任务得进行排队,一个接着一个被引擎处理.

GUI渲染也是在引擎线程中执行的,脚本中执行对界面进行更新操作,如添加结点,删除结点或改变结点的外观等更新并不会立即体现出来,这些操作将保存在一个队列中,待JavaScript引擎空闲时才有机会渲染出来。来看例子(这块内容还有待验证,个人觉得当Dom渲染时,才可阻止渲染)

<div id="test">test</div>

<script type="text/javascript" language="javascript">

var i=0;

while(1) {

document.getElementById("test").innerHTML+=i++ + "<br />";

}

</script>

  这段代码的本意是从0开始顺序显示数字,它们将一个接一个出现,现在我们来仔细研究一下代码,while(1)创建了一个无休止的循环,但是对于单线程的JavaScript引擎而言,在实际情况中就会造成浏览器暂停响应并处于假死状态。

  alert()会停止JS引擎的执行,直到按确认键,在JS调试的时候,查看当前实时页面的内容。

4.       setTimeout和 setInterval

回到文章开头,我们来看下setTimeout和setsetInterval的区别。

setTimeout(function(){

/* Some long block of code ... */

setTimout(arguments.callee,10);

},10);

setInterval(function(){

/* Some long block of code ... */

},10);

  这两个程序段第一眼看上去是一样的,但并不是这样。setTimeout代码至少每隔10ms以上才执行一次;然而setInterval固定每隔10ms将尝试执行,不管它的回调函数的执行状态。

我们来总结下:

l JavaScript引擎只有一个线程,强制异步事件排队等待执行。

l setTimeout和setInterval在异步执行时,有着根本性不同。

l 如果一个计时器被阻塞执行,它将会延迟,直到下一个可执行点(这可能比期望的时间更长)

l setInterval的回调可能被不停的执行,中间没间隔(如果回调执行的时间超过预定等待的值)

《JavaScript高级程序设计》中,针对setInterval说法如下:

当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。还要注意两问题:

①    某些间隔会被跳过(抛弃);

② 多个定时器的代码执行之间的间隔可能会比预期小。此时可采取 setTimeout和setsetInterval的区别 的例子方法。

5.       Ajax异步

  很多同学朋友搞不清楚,既然说JavaScript是单线程运行的,那么XMLHttpRequest在连接后是否真的异步?其实请求确实是异步的,不过这请求是由浏览器新开一个线程请求(参见上图),当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到JavaScript引擎的处理队列中等待处理,当任务被处理时,JavaScript引擎始终是单线程运行回调函数,具体点即还是单线程运行onreadystatechange所设置的函数。

  Tip:理解JavaScript引擎运作非常重要,特别是在大量异步事件(连续)发生时,可以提升程序代码的效率

转:https://blog.csdn.net/qdq2014/article/details/72383725

js-关于异步原理的理解和总结的更多相关文章

  1. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

  2. JavaScript 事件循环及异步原理(完全指北)

    引言 最近面试被问到,JS 既然是单线程的,为什么可以执行异步操作? 当时脑子蒙了,思维一直被困在 单线程 这个问题上,一直在思考单线程为什么可以额外运行任务,其实在我很早以前写的博客里面有写相关的内 ...

  3. 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制

    转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...

  4. js异步原理与 Promise

    一.Javascript的异步原理 javascript 是单线程语言,所以同一时间只执行一个运算.但有些方法是不能瞬间完成或不可预知何时完成的(如网络请求.settimeout等),为了让它们不对后 ...

  5. Node.js机制及原理理解初步【转】

    一.node.js优缺点 node.js是单线程. 好处就是 1)简单 2)高性能,避免了频繁的线程切换开销 3)占用资源小,因为是单线程,在大负荷情况下,对内存占用仍然很低 3)线程安全,没有加锁. ...

  6. Node.js机制及原理理解初步

    http://blog.csdn.net/leftfist/article/details/41891407 一.node.js优缺点 node.js是单线程. 好处就是 1)简单 2)高性能,避免了 ...

  7. js异步队列之理解

    起因 最近看到一篇关于js异步执行顺序的解答,觉得有所收获,遂记录下来. marcotask和microtask js中异步队列可以分为两类,marcotask队列和microtask队列, marc ...

  8. js异步原理

    javascript引擎 javascript引擎执行时单线程的,如果有一个任务被阻塞,整个浏览器就会处于假死状态 那么遇到这种情况,又在单线程的情况下,不能像java之类的语言创建多几个线程来处理, ...

  9. js的异步和单线程

    最近,同事之间做技术分享的时候提到了一个问题"js的异步是另开一个线程吗?"当时为此争论不休.会后自己查阅了一些资料,对这个问题进行一个自我的分析与总结,有不同意见的希望可以赐教, ...

随机推荐

  1. RabbitMQ实战场景(一):异步记录用户操作日志

    传统的项目开发中业务流程以串行方式,执行了模块1—>模块2–>模块3 而我们知道,这个执行流程其实对于整个程序来讲是有一定的弊端的,主要有几点: (1)整个流程的执行响应等待时间比较长; ...

  2. 小米cc9和vivo z5 对比

    (一)大致对比 1.小米 cc9(6GB+128GB.  1899元. 白色恋人(白色)) https://item.mi.com/product/10000163.html 2.vivo Z5 (6 ...

  3. Deep learning_CNN_Review:A Survey of the Recent Architectures of Deep Convolutional Neural Networks——2019

    CNN综述文章 的翻译 [2019 CVPR] A Survey of the Recent Architectures of Deep Convolutional Neural Networks 翻 ...

  4. 使用awstats分析nginx日志

    1.awstats介绍 本文主要是记录centos6.5下安装配置awstats,并统计nginx访问日志 1.1 awstats介绍 awstats是一款日志统计工具,它使用Perl语言编写,可统计 ...

  5. ssh无密码连接

    1. 生成密钥对文件 [root@centos2 ~]# -t 指定加密类型 -b 指定密钥对加密长度 询问1:执行过程中会询问保存位置,一般默认保存在当前用户家目录下的.ssh/目录下 询问2:是否 ...

  6. WebSocket使用中Stomp Client连接报ERROR CODE 200的解决办法

    [转] https://www.cnblogs.com/chrischeng/p/10237523.html 最近在做一个WebSocket项目,后台使用了Stomp协议的WebSocketConfi ...

  7. docker 基本常用操作做

    docker 基本常用操作做(只列举入门常用的命令) 容器生命周期管理 docker run :创建一个新的容器并运行一个命令 -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDO ...

  8. 在eclipse运行一个项目报端口被占的问题

    1.端口被占问题解决方法. 我们运行javaweb项目的时候,如果不幸你的项目出现了上图的那种情况,不要慌,仅仅是端口被占了而已,只需要打开你tomcat里面的bin里面的shutdown.bat即可 ...

  9. sublime——开启自动保存

    前言 懒 步骤 失去焦点自动保存 "save_on_focus_lost": true 首选项-->设置-->Ctrl+F搜索‘save’,找到“save_on_foc ...

  10. Mysql 查询当天、昨天、近7天、一周内、本月、上一月等的数据(函数执行日期的算术运算)

    注:where语句后中的字段last_login_time 替换成 时间字段名 即可 #查询昨天登录用户的账号 ; #查询当天登录用户的账号 ; #查询所有last_login_time值在最后1天内 ...