先来思考一个问题,JS 是单线程的还是多线程的?如果是单线程,为什么JavaScript能让AJAX异步发送和回调请求,还有setTimeout也看起来像是多线程的?还有non-blocking IO, event loop等概念。


目录:

  1. 单线程的 JS 引擎
  2. 多线程的浏览器
  3. setTimeout(func, 0) 的应用场景
  4. setTimeout与setInterval
  5. [更新]参考资料
  6. [更新]深入:JS并发模型与 Event Loop

1. 单线程的 JS 引擎

浏览器无论在什么时候都只有一个线程在运行JavaScript程序。单线程,即在某个特定的时刻只有特定代码能执行,并阻塞其他代码。

而浏览器是事件驱动的(event-driven),可以将事件看做浏览器派给JS引擎的任务,

这些任务可能来自JS引擎当前正在执行的代码块,比如说,调用 setTimeout() 添加一个任务;

或者是来自浏览器内核的其他线程,比如说,界面元素鼠标点击事件、定时器时间到达通知、异步请求状态变更通知(a mouse click, a timer firing, or an XMLHttpRequest completing),

从代码角度看,任务实体就是各种回调函数。因为JS引擎是单线程的,这些任务得排队(加入JS引擎的处理队列),等待被JS引擎执行。

  • 好处:

如果多线程,那么删除或者创建dom元素,都需要在线程之间通信。

所以,单线程简单,不需要考虑线程同步;没有线程切换维护开销,省内存。


2. 多线程的浏览器

浏览器内核实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步。

浏览器内核可能有如下线程:

常驻线程:

  • 界面渲染线程
  • 事件响应线程

执行完就终止的线程:

  • Http请求线程
  • 定时器线程

一张图说明单线程的JS引擎如何与其他线程通信:

  • JS引擎线程 与 界面渲染线程:互斥

界面渲染线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行.
JavaScript脚本可操纵DOM元素,如果在修改这些元素属性的同时渲染界面,那么渲染线程前后获得的元素数据就可能不一致了。
在JavaScript引擎运行脚本期间,浏览器渲染线程都是处于挂起状态的,也就是说被”冻结”了.
所以,在脚本中对界面进行更新操作,如添加结点、删除结点或改变结点的外观等更新并不会立即体现出来,这些操作将保存在一个队列中,待JavaScript引擎空闲时才有机会渲染出来.

  • 事件触发线程

用户点击鼠标 -》 被浏览器的事件触发线程捕获 -》 形成一个鼠标点击事件添加到 JS引擎的处理队列 末尾

  • 定时触发线程

定时计数器并不是由JS引擎计数的,因为JS引擎单线程,如果处于线程阻塞状态就记不了时,需要依赖外部计数器。

定时器仅仅只是计划代码在未来的某个时间执行,而执行时机是不能保证的。

它的工作方式是:指定一个时间间隔,表示何时将定时器的代码插入到 JS引擎的处理队列,而不是何时执行代码。其等待的时间基于队列里正在等待的消息数量。

  • ajax 异步请求

浏览器新开一个线程请求,当请求状态改变时,若已设置回调(即 onreadystatechange 中设置的回调函数),该异步线程则将状态变更事件放入 JS引擎的处理队列 末尾等待处理。


3. setTimeout(func, 0) 的应用场景

它告诉js引擎,在0ms以后把func放到主事件队列中,等待当前的代码执行完毕再执行,注意:重点是改变了代码流程,把func的执行放到了等待当前的代码执行完毕再执行。

  • 让浏览器渲染当前的变化(很多浏览器UI render和js执行是放在一个线程中,线程阻塞会导致界面无法更新渲染)【待理解】
  • 重新评估”script is running too long”警告【待理解】
  • 改变执行顺序

下例中,点击按钮就会显示"calculating....",如果删除setTimeout就不会。因为reDraw事件被进入事件队列,到长时间操作的最后才能被执行,所以无法刷新。

 <button id='do'> Do long calc!</button>
<div id='status'></div>
<div id='result'></div> $('#do').on('click', function(){ $('#status').text('calculating....'); //此处会触发redraw事件的fired,但会放到队列里执行,直到long()执行完。 // without set timeout, user will never see "calculating...."
//long();//执行长时间任务,阻塞 // with set timeout, works as expected
setTimeout(long,50);//用定时器,大约50ms以后执行长时间任务,放入执行队列,但在redraw之后了,根据先进先出原则 }) function long(){
var result = 0
for (var i = 0; i<1000; i++){
for (var j = 0; j<1000; j++){
for (var k = 0; k<1000; k++){
result = result + i+j+k
}
}
}
$('#status').text('calclation done') // has to be in here for this example. or else it will ALWAYS run instantly. This is the same as passing it a callback
}

4. setTimeout与setInterval

 setTimeout(function() {
/* 代码块... */
setTimeout(arguments.callee, 10);
}, 10); setInterval(function(){
/*代码块... */
}, 10);

这两段代码看一起效果一样,其实非也,第一段中回调函数内的 setTimeout 是 JS引擎 执行后再设置新的setTimeout定时,假定上一个回调处理完到下一个回调开始处理为一个时间间隔,理论上两个setTimeout回调的执行时间间隔>=10ms。第二段自 setInterval 设置定时后,定时触发线程就会源源不断的每隔十秒产生异步定时事件,并放到任务队列尾,理论上两个setInterval回调执行时间间隔<=10。


5. 参考资料

深入理解JavaScript定时机制

Javascript是单线程的深入分析

How JavaScript Timers Work

JavaScript 高级程序设计(第三版) 22.3 高级定时器

[更新]

MDN:并发模型与Event Loop

JavaScript 运行机制详解:再谈Event Loop

【赞!必看】Philip Roberts的演讲《Help, I'm stuck in an event-loop


6. 深入:JS并发模型与 Event Loop

JS中的并发模型基于"Event Loop"。

先来看一个理论上的模型:(下图来自MDN:并发模型与Event Loop

  • 堆(Heap):内存中一块大的、未被组织的空间,对象被创建在堆中。
  • 栈(Stack):函数调用的空间。栈结构具有的特点:先进后出,从栈顶压入、栈顶弹出,恰好可以对应函数的调用过程。换句话说,函数的调用、返回,即压栈、弹栈的过程。
  • 消息队列(Queue):也称任务队列/ JS引擎的处理队列...(随你咯)。

每个消息都与一个函数关联。

当栈为空时,从队列中取出一个消息进行处理。处理过程包含:调用与消息相关联的函数,创建一个初始栈结构。

事件循环(Event Loop):(下图来自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)

JS引擎运行时需要用到堆和栈。当栈为空时,读取消息队列,若有消息,即调用与消息相关联的函数,并初始化栈结构,执行这个函数过程中涉及到的函数调用都通过压栈弹栈来实现。而栈中代码又可能调用外部API(DOM事件,ajax,定时器),于是消息队列中又添加了新的消息,等待栈空时被JS引擎读取执行。于是构成了 Event Loop.

借助 MDN:并发模型与Event Loop 中对 Event Loop 中的阐述,个人理解是

1 while( (stack is Empty) && (queue.hasMessage) ) {
2 queue.processNextMessage;
3 } 

事件“循环”这个名字很形象~

那么,关于非阻塞I/O(non-blocking I/O),应该很清楚了,以ajax为例,通过外部API建立一个新连接,当相应状态变化时,触发状态变化事件,加入到 JS引擎 的消息队列末尾,等待执行(栈空,且队列前无其他消息等待执行)。在等待连接响应的期间,JS引擎正常、欢快的执行着其他代码呢。

[更新]单线程的JS引擎与 Event Loop的更多相关文章

  1. 为什么JS是单线程?JS中的Event Loop(事件循环)?JS如何实现异步?setimeout?

    https://segmentfault.com/a/1190000012806637 https://www.jianshu.com/p/93d756db8c81 首先,请牢记2点: (1) JS是 ...

  2. js in depth: event loop & micro-task, macro-task & stack, queue, heap & thread, process

    js in depth: event loop & micro-task, macro-task & stack, queue, heap & thread, process ...

  3. node.js中对Event Loop事件循环的理解

    javascript是单线程的,所以任务的执行都需要排队,任务分为两种,一种是同步任务,一种是异步任务. 同步任务是进入主线程上排队执行的任务,上一个任务执行完了,下一个任务才会执行. 异步任务是不进 ...

  4. Js 运行机制 event loop

    Js - 运行机制 (Even Loop) Javascript 的单线程 - 引用思否的说法: JavaScript的一个语言特性(也是这门语言的核心)就是单线程.什么是单线程呢?简单地说就是同一时 ...

  5. JS事件循环(Event Loop)机制

    前言 众所周知,为了与浏览器进行交互,Javascript是一门非阻塞单线程脚本语言. 为何单线程? 因为如果在DOM操作中,有两个线程一个添加节点,一个删除节点,浏览器并不知道以哪个为准,所以只能选 ...

  6. [Node.js] Use nodejs-dashboard event loop delay with hrtime()

    In this lesson, you will learn how to use the Formidable nodejs-dashboard event loop delay to identi ...

  7. 再次聊一聊promise settimeout asycn awiat执行顺序---js执行机制 EVENT LOOP

    首先js是单线程 分为同步和异步,异步又分为(macrotask 宏任务 和 microtask微任务 ), 这图还是很清晰嘛,再来一张 总结一下,就是遇到同步先执行同步,异步的丢到一边依次排队,先排 ...

  8. 浅析Node.js的Event Loop

    目录 浅析Node.js的Event Loop 引出问题 Node.js的基本架构 Libuv Event Loop Event Loop Phases Overview Poll Phase The ...

  9. js为什么是单线程的?10分钟了解js引擎的执行机制

    深入理解JS引擎的执行机制 1.JS为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.说说setT ...

随机推荐

  1. 【一天一道LeetCode】#103. Binary Tree Zigzag Level Order Traversal

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 来源: htt ...

  2. Unity UGUI基础之Image

    UGUI的Image等价于NGUI的Sprite组件,用于显示图片. 一.Image组件: Source Image(图像源):纹理格式为Sprite(2D and UI)的图片资源(导入图片后选择T ...

  3. 一个CSS+jQuery的放大缩小动画效果

    日期: 2013年9月23日 作者:铁锚 // 今天帮朋友写了一些代码,自己觉得写着写着,好几个版本以后,有点满意,于是就贴出来. // 都是定死了的.因为需求就只有4个元素.如果是要用CSS的cla ...

  4. pycharm+django之小试牛刀

    准备好好学习一下python,就从django开始吧,顺带了解一下网站的开发.今天在windows上安装了python,django,以及酷炫吊的IDE--pycharm,学习资料主要是<the ...

  5. Uva - 810 - A Dicey Problem

    根据状态进行bfs,手动打表维护骰子滚动. AC代码: #include <iostream> #include <cstdio> #include <cstdlib&g ...

  6. 【Django】优化小技巧之清除过期session

    事情是这样的,大概也就几万注册用户的站点(使用django1.6), session 存储在关系型数据库,这次上线之后发现session表几十万数据了,过期session没有被自动删除 思考 官网 s ...

  7. NSDate-日期类&nbsp;OC——第七天(1)

    1.总结前面学习的Oc中的几种数据类型 NSInteger NSUinteger CGFloat NSString NSMutableString NSSArray NSMutableArray NS ...

  8. linux 下启动java jar包 shell

    linux 下启动java jar包 shell #!/bin/sh JAVA_HOME=/usr/local/jdk1.6.0_34/bin/javaJAVA_OPTS="-Xmx256m ...

  9. JavaScript进阶(三)常见工具(校验、通用)

    JS常见工具(校验.通用) // 姓名校验 var checkName = function(name) { // 收货人姓名校验(准则:姓名为2-4汉字) var regu = /^[\u4E00- ...

  10. TrueType和Bitmap字体的区别

    只要标签的文本从不变化,在cocos2D中渲染TrueType和bitmap字体的性能是相同的.它们都仅仅像精灵那样绘制. 如果你希望大量的标签使用相同字体,则bitmap字体将更快.因为bitmap ...