先来思考一个问题,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. Android的stateListDrawable,layerDawable,clipdrawable,AnimationDarwable介绍-android学习之旅(五十五)

    StatelistDrawable资源 代码示例 <?xml version="1.0" encoding="utf-8"?> <select ...

  2. 【一天一道LeetCode】#115. Distinct Subsequences

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

  3. Touch Handling in Cocos2D 3.x(二)

    接受触摸 在Cocos2d 3.0中每一个CCNode和每一个CCNode的子类都可以接收触摸.你只需要开启一个选项.让我们在定制的初始化器里完成它.替换MainScene.m中init方法的代码: ...

  4. 【一天一道LeetCode】#61. Rotate List

    一天一道LeetCode系列 (一)题目 Given a list, rotate the list to the right by k places, where k is non-negative ...

  5. Java应用服务器Resin

    Resin是CAUCHO公司(http://www.caucho.com/)的产品,是一个非常流行的支持servlets 和jsp的引擎,速度非常快.Resin本身包含了一个支持HTTP/1.1的WE ...

  6. Cygwin获取root权限

    .启动cygwin进入以后,就会以真正的root权限运行了.

  7. mysql进阶(二十三)数据库事务四大特性

    数据库事务四大特性       原子性.一致性.分离性.持久性 原子性  事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据修改操作要么全部执行,要么完全不执行.这种特性称为 ...

  8. 14_Android中Service的使用,关于广播接收者的说明

     服务:长期后台运行的没有界面的组件 android应用:什么地方需要用到服务? 天气预报:后台的连接服务器的逻辑,每隔一段时间获取最新的天气信息 股票显示:后台的连接服务器的逻辑,每隔一段时间获 ...

  9. libgdx 1.4.1发布

    (转载自http://www.libgdx.cn/topic/4/libgdx-1-4-1%E5%8F%91%E5%B8%83) libgdx从未停止进步的脚步.10月10日,libgdx1.4.1发 ...

  10. (三十二)DatePicker和自定义键盘

    DatePicker通过设置Locale属性可以设置语言(注意手机语言也会影响到它的显示). 如果通过代码创建DatePicker,又要设置属性,使用下面的代码,注意locale是个枚举,初始化要填写 ...