这个系列的文章名为“JavaScript 进阶”,内容涉及JS中容易忽略但是很有用的,偏JS底层的,以及复杂项目中的JS的实践。主要来源于我几年的开发过程中遇到的问题。小弟第一次写博客,写的不好的地方请诸位斧正,觉得还有一些阅读价值的请帮忙分享下。这个“JavaScript 进阶”是一个系列文章,请大家鼓励鼓励,我尽快更新。另外,如果你有比较好的话题,也可以在下面评论,我们一起研究提高。

JS是多线程的吗?

多线程编程相信大家都很熟悉,比如在界面开发中,如果一个事件的响应需要较长时间,那么一般做法就是把事件处理程序写在另外一个线程中,在处理过程中,在界面上面显示类似进度条的元素。这样界面就不会卡住,并且能够显示任务执行进度。记得刚开始做前端的时候,老板交代在界面上面做一个定时器,每秒更新用户的在线时间。当时拥有Java和C++开发经验的我自信满满的说我加一个线程就可以分分钟搞定了。所以查阅文档,发现setTimeout和setInterval可以很方便的实现该功能。那时候我就认为这就是JS中的多线程。setTimeout相当于启动一个线程,等待一段时间后执行函数,setInterval则是在另外的一个线程中,每隔一段时间执行函数。这个观念在我的头脑中存在了一年左右,直到遇到了这样的一个问题。

测试人员发现一个按钮的点击响应时间较长,在响应过程中,界面卡住了,我检查代码发现代码中做了这样的事情。

  1. $("#submit").on("click", function() {
  2. bigTask(); // 这个函数需要较长时间来执行
  3. })

所以我想很简单啊,把这个函数放在另外的一个线程执行就好了啊,所以代码改成了这样, 以为可以轻松解决问题。但是事实上发现毫无用处,界面还是原来一样的行为,点击按钮之后卡住了几秒。

  1. $("#submit").on("click", function() {
  2. setTimeout(function() {
  3. bigTask()
  4. }, 0)
  5. })

这个问题我百思不得其解,最后多方查阅资料才明白了如下的内容:

  1. 浏览器中的JS是单线程的。
  2. setInterval和setTimeout并不是多线程,这两个函数根本上其实是事件触发函数

想证明setInterval和setTimeout不是多线程很简单,你可以试试这样一段代码

  1. setTimeout(function() {
  2. while(true){}
  3. }, 0)
  4. setTimeout(function() {
  5. alert("foo")
  6. }, 1000)

不出意外,你的浏览器无法响应了,页面上面的按钮不能点,Gif也不能动,那个alert肯定也出不来。这是为什么呢?

为了解释上面的问题,我们来深入解析一下浏览器。浏览器中有三个常驻的线程,分别是JS引擎,界面渲染,事件响应。由于这三个线程同时要访问DOM树,所以为了线程安全,浏览器内部需要做互斥:当JS引擎在执行代码的时候,界面渲染和事件响应两个线程是被暂停的。所以当JS出现死循环,浏览器无法响应点击,也无法更新界面。现在的浏览器的JS引擎都是单线程的,尽管多线程功能强大,但是线程同步比较复杂,并且危险,稍有不慎就会崩溃死锁。单线程的好处不必考虑线程同步这样的复杂问题,简单而安全。下面的一幅图来简要说明JS引擎的执行流程:

JS引擎基于事件来执行代码。事件响应线程在接到事件后,把响应的代码放到JS引擎的队列中,JS引擎按顺序执行代码。在JS引擎没有代码可以执行的时候,比如图中蓝色方框的间隙中,事件线程和渲染线程得以有机会运行。基于这些信息,能够的出下面的结论

  1. setTimeout,setInterval并不是多线程,只是一个定时的事件触发器,它们在合适的时间把一些JS代码塞到JS引擎的队列中。
  2. setTimeout(aFunction, 0),这行代码看似的意思是在0秒之后执行aFunction, 但这并不意味着立即执行。其它真正的意思是立刻把aFunction的代码放到当前JS引擎的队列中。所以当前代码块执行完成之前,aFunction的代码是得不到执行的。比如这段代码,一定是world先出来,hello后出来。尽管setTimeout的参数是0,但这并不意味着立即执行
    1. setTimeout(function() {
    2. alert("hello");
    3. }, 0)
    4. alert("world")
  3. 在一个事件的响应代码执行完成之后,即使队列中有待执行的代码,浏览器也会先执行页面渲染和响应事件,完成之后再执行队列中的代码。

异步Ajax

看到这边相信各位应该对JS的单线程以及setTimeout,setInterval的本质有所了解了,那么我们再继续讨论下一个问题,异步Ajax。上文说了,JS是单线程的,当一个函数执行的时候,JS引擎会锁住DOM树,其他事件的响应代码只能在队列中等待,并且此时页面卡死。那么异步Ajax是怎么回事呢?一个常用的开发实践就是发起一个异步的Ajax,界面显示一个进度条样式的Gif,说好的单线程呢?事实上异步Ajax确实用了多线程,只是Ajax请求的Http连接部分由浏览器另外开了一个线程执行,执行完毕之后给JS引擎发送一个事件,这时候异步请求的回调代码得以执行。它的执行流程是这样的:

Http请求的执行在另外一个线程中,由于这个线程并不会操作DOM树,所以是可以保证线程安全的。发起Ajax请求和回调函数中间是没有JS执行的,所以页面不会卡死。

真正的多线程JS

在HTML5中,引入了Web Worker这个概念。它能够在另外一个线程中执行计算密集的JS代码而不引起页面卡死,这是真正的多线程。然而为了保证线程安全,Worker中的代码是不能访问DOM的。其具体使用方法在此不作赘述,请参考:http://www.w3school.com.cn/html5/html_5_webworkers.asp

总结

结合上面的分析,总结出出下面的一些实践供各位参考。

    1. 避免编写计算密集的前端代码。
    2. 使用异步Ajax。
    3. 避免编写一个需要较长时间来执行的JS代码,比如生成一个大型的表。遇到这种情况,可以分批执行,比如用setInterval来每秒生成20行,或是用户向下拖动滚动条时候再继续产生新的行。
    4. 在页面初始化时候不要执行很多的初始化代码,否则会影响页面渲染变慢。一些不需要立即执行的代码可以在页面渲染完成之后再执行,比如绑定事件,生成菜单之类的控件。
    5. 对于复杂页面(像淘宝首页),可以结合异步Ajax分批产生页面。先生成页面框架,页面内容自上而下用异步Ajax逐步加载并填充到框架中。这样能够让用户更早的看到页面。
    6. setTimeout(function, 0)是有用的。它可以让callback作为另外一个事件响应代码来执行。实现了当前事件的代码执行完成之后,再渲染DOM,再执行setTimeout的callback。这样能够让一部分代码延后执行,并且在这之前渲染DOM。

JavaScript 进阶(一)JS的"多线程"的更多相关文章

  1. JavaScript进阶(九)JS实现本地文件上传至阿里云服务器

    JS实现本地文件上传至阿里云服务器 前言 在前面的博客< JavaScript进阶(八)JS实现图片预览并导入服务器功能>(点击查看详情)中,实现了JS将本地图片文件预览并上传至阿里云服务 ...

  2. JavaScript进阶(八)JS实现图片预览并导入服务器功能

    JS实现导入文件功能       赠人玫瑰,手留余香.若您感觉此篇博文对您有用,请花费2秒时间点个赞,您的鼓励是我不断前进的动力,共勉!(PS:此篇博文是自己在午饭时间所写,为此没吃午饭,这就是程序猿 ...

  3. 二、JavaScript语言--JS基础--JavaScript进阶篇--JS基础语法

    1.变量 定义:从字面上看,变量是可变的量:从编程角度讲,变量是用于存储某种/某些数值的存储器.我们可以把变量看做一个盒子,盒子用来存放物品,物品可以是衣服.玩具.水果...等. 命名:变量名字可以任 ...

  4. Javascript进阶篇——(JS基础语法)笔记整理

    根据慕课网学习整理到一起的笔记,把东西整理到一起看起来比较方便 什么是变量字面意思:变量是可变的量:编程角度:变量是用于存储某种/某些数值的存储器.我们可以把变量看做一个盒子,盒子用来存放物品,物品可 ...

  5. JavaScript进阶(七)JS截取字符串substr 和 substring方法的区别

    JS截取字符串substr 和 substring方法的区别 substr方法 返回一个从指定位置开始的指定长度的子字符串. stringvar.substr(start [, length ]) 参 ...

  6. JavaScript进阶(五)js中取小数整数部分函数

    js中取小数整数部分函数 丢弃小数部分,保留整数部分 js:parseInt(7/2) 向上取整,有小数就整数部分加1 js: Math.ceil(7/2) 四舍五入 js: Math.round(7 ...

  7. JavaScript进阶(四)js字符串转换成数字的三种方法

    js字符串转换成数字的三种方法 在js读取文本框或者其它表单数据的时候获得的值是字符串类型的,例如两个文本框a和b,如果获得a的value值为11,b的value值为9 ,那么a.value要小于b. ...

  8. JavaScript进阶(二)在一个JS文件中引用另一个JS文件

    在一个JS文件中引用另一个JS文件       转载地址:http://blog.csdn.net/zndxlxm/article/details/7875787 方法一 在调用文件的顶部加入下例代码 ...

  9. web Worker使js实现‘多线程’?

    大家都知道js是单线程的,在上一段js执行结束之前,后面的js绝对不会执行,那么为什么标题说js实现‘多线程’,虽然说加了引号,可是标题也不能乱写不是,可恶的标题党? 姑且抛开标题不说,先说我们经常会 ...

随机推荐

  1. 手势滑动结束 Activity(一)基本功能的实现

    喜欢听音乐的朋友可能都看过天天动听这款 app, 这款 app 有一个亮点就是在切换页面(Fragment)的时候能够通过手势滑动来结束当前页面.这里先说一下,我为什么会这么关心这个功能呢,由于前两天 ...

  2. android 网络交互

    一. 在Android中,发送和处理http请求实在太常见了,以至于我们经常需要写这方面的代码. Android中网络交互的代码不能在UI线程中执行,只能在额外的子线程中执行. 我一般的做法是通过创建 ...

  3. Android自定义照相机实现(拍照、保存到SD卡,利用Bundle在Acitivity交换数据)

    Android自定义照相机实现 近期小巫在学校有一个创新项目,也不是最近,是一个拖了很久的项目,之前一直没有去搞,最近因为要中期检查,搞得我跟小组成员一阵忙活,其实开发一款照相机软件并不太难,下面就是 ...

  4. mysql 行锁排查

    <pre name="code" class="html">mysql 锁表: 隔离级别使用RR: mysql> SELECT @@GLOBA ...

  5. UVALive 6584 Escape (Regionals 2013 >> Europe - Central)

    题目 给出一棵树,每个节点有一个怪物或血药,遇到怪物必须打,打完后扣掉一定的血量. 一开始英雄的血量为\(0\),当血量小于\(0\)时就挂了. 给出英雄的起点和终点,问能否成功到达终点. 算法 这题 ...

  6. 解说cocos2d-x几种画图方法的用法与思考

    CCRenderTexture 自己的理解 CCRenderTexture类似一张空白的“画布“,用户通过自定义笔刷(CCSprite*),在touch事件中把笔刷的移动痕迹“记录”起来,从而“画”出 ...

  7. servlet 与缓存(4)

    1.设置缓存的两种情况: 第一种:对于不常常变化的数据,在servlet中能够为其设置合理的缓存时间值,以避免浏览器频繁向server发送请求,提升server的性能. 另外一种:假设要实现一种高级功 ...

  8. BZOJ 2705: [SDOI2012]Longge的问题( 数论 )

    T了一版....是因为我找质因数的姿势不对... 考虑n的每个因数对答案的贡献. 答案就是 ∑ d * phi(n / d) (d | n) 直接枚举n的因数然后求phi就行了. 但是我们可以做的更好 ...

  9. Goodle Clean设计架构

    Goodle Clean设计架构 23 * @param <P> the response type 24 */ 25 public abstract class UseCase<Q ...

  10. hihocoder1302 最长回文子串

    hihocoder1302 最长回文子串 先贴代码 所有的上面的提示已经交代的好清楚了…… #include <iostream> #include <cstring> #in ...