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

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

JS是多线程的吗?

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

 

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

 

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

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

 

 

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

 

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

 

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

想证实setInterval和setTimeout不是多线程很简单,你能够试试这样一段代码java

 

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

 

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

为了解释上面的问题,咱们来深刻解析一下浏览器。浏览器中有三个常驻的线程,分别是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。
相关文章
相关标签/搜索