JavaScript 进阶(一)JS的"多线程"

这个系列的文章名为“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。
时间: 01-16

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

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

JS实现本地文件上传至阿里云服务器 前言 在前面的博客< JavaScript进阶(八)JS实现图片预览并导入服务器功能>(点击查看详情)中,实现了JS将本地图片文件预览并上传至阿里云服务器的操作.这次需要实现将本地打包好的文件上传至阿里云服务器.使用前面的图片文件上传方法无法完成此操作.操作界面如下: 思路 本地与服务端传输文件的格式应该是熟悉的Base64格式.首先需要将本地文件转换为Base64格式,传输至服务端后,在服务端再将Base64格式的文件转换为原始文件. 源码解析 控制器 /

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

根据慕课网学习整理到一起的笔记,把东西整理到一起看起来比较方便 什么是变量字面意思:变量是可变的量:编程角度:变量是用于存储某种/某些数值的存储器.我们可以把变量看做一个盒子,盒子用来存放物品,物品可以是衣服.玩具.水果...等. 表达出你的想法(表达式)表达式与数学中的定义相似,表达式是指具有一定的值.用操作符把常数和变量连接起来的代数式.一个表达式可以包含常数或变量. 字符串表达式:"I" + "love" + "you" + mychar

javascript进阶笔记(2)

js是一门函数式语言,因为js的强大威力依赖于是否将其作为函数式语言进行使用.在js中,我们通常要大量使用函数式编程风格.函数式编程专注于:少而精.通常无副作用.将函数作为程序代码的基础构件块. 在函数式编程中,有一种函数称为匿名函数,也就是没有名称的函数,是js中的一个非常重要的概念.通常匿名函数的使用情况是,创建一个供以后使用的函数.比如将匿名函数保存在一个变量里面,或将其作为一个对象方法,更有甚者将其作为一个回调等等之类的. //保存在变量中,通过fn去引用 var fn=function

创业笔记-Node.js入门之JavaScript与Node.js

JavaScript与Node.js JavaScript与你 抛开技术,我们先来聊聊你以及你和JavaScript的关系.本章的主要目的是想让你看看,对你而言是否有必要继续阅读后续章节的内容. 如果你和我一样,那么你很早就开始利用HTML进行“开发”,正因如此,你接触到了这个叫JavaScript有趣的东西,而对于JavaScript,你只会基本的操作——为web页面添加交互. 而你真正想要的是“干货”,你想要知道如何构建复杂的web站点 —— 于是,你学习了一种诸如PHP.Ruby.Java

JavaScript进阶系列06,事件委托

在"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数"中已经有了一个跨浏览器的事件处理机制.现在需要使用这个事件处理机制为页面元素注册事件方法. □ 点击页面任何部分触发事件 创建一个script1.js文件. (function() { eventUtility.addEvent(document, "click", function(evt) { alert('hello'); }); }(

JavaScript进阶(一)

OK接下来,我们再次梳理一遍js并且提高一个等级. 众所周知,web前端开发者需要了解html和css,会只用html和css创建一个漂亮的页 面,但是这肯定是不够的,因为它只是一个静态的页面,我们还需要用JavaScript 增加它的行为,为网页添加动态效果. 首先,JavaScript能做什么? 1.增强页面动态效果(如:下拉菜单,图片轮播,信息滚动等) 2.实现页面与用户之间的实时,动态交互(如:用户注册,登录验证等) JavaScript进阶篇章能学习到什么呢? 在JavaScript入

JavaScript 进阶篇的学习~

---恢复内容开始--- 让你认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂亮的页面,但这还不够,它只是静态页面而已.我们还需使用JavaScript增加行为,为网页添加动态效果.准备好,让JavaScript带你进入新境界吧! JavaScript能做什么? 1.增强页面动态效果(如:下拉菜单.图片轮播.信息滚动等) 2.实现页面与用户之间的实时.动态交互(如:用户注册.登陆验证等

JavaScript进阶系列07,鼠标事件

鼠标事件有Keydown, Keyup, Keypress,但Keypress与Keydown和Keyup不同,如果按ctrl, shift, caps lock......等修饰键,不会触发Keypress事件,而会触发Keydown和Keyup事件,这就是Keypress事件与Keydown.Keyup事件的不同之处.另外,通常使用Keypress事件来获取用户输入信息. 继续使用"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件

JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数

本篇体验JavaScript事件的基本面,包括: ■ 事件必须在页面元素加载之后起效■ 点击事件的一个简单例子■ 为元素注册多个点击事件■ 获取事件参数 □ 事件必须在页面元素加载之后起效 有这样一段简单的代码: <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <style>