JavaScript 进阶(一)JS的"多线程"
这个系列的文章名为“JavaScript 进阶”,内容涉及JS中容易忽略但是很有用的,偏JS底层的,以及复杂项目中的JS的实践。主要来源于我几年的开发过程中遇到的问题。小弟第一次写博客,写的不好的地方请诸位斧正,觉得还有一些阅读价值的请帮忙分享下。这个“JavaScript 进阶”是一个系列文章,请大家鼓励鼓励,我尽快更新。另外,如果你有比较好的话题,也可以在下面评论,我们一起研究提高。
JS是多线程的吗?
多线程编程相信大家都很熟悉,比如在界面开发中,如果一个事件的响应需要较长时间,那么一般做法就是把事件处理程序写在另外一个线程中,在处理过程中,在界面上面显示类似进度条的元素。这样界面就不会卡住,并且能够显示任务执行进度。记得刚开始做前端的时候,老板交代在界面上面做一个定时器,每秒更新用户的在线时间。当时拥有Java和C++开发经验的我自信满满的说我加一个线程就可以分分钟搞定了。所以查阅文档,发现setTimeout和setInterval可以很方便的实现该功能。那时候我就认为这就是JS中的多线程。setTimeout相当于启动一个线程,等待一段时间后执行函数,setInterval则是在另外的一个线程中,每隔一段时间执行函数。这个观念在我的头脑中存在了一年左右,直到遇到了这样的一个问题。
测试人员发现一个按钮的点击响应时间较长,在响应过程中,界面卡住了,我检查代码发现代码中做了这样的事情。
- $("#submit").on("click", function() {
- bigTask(); // 这个函数需要较长时间来执行
- })
所以我想很简单啊,把这个函数放在另外的一个线程执行就好了啊,所以代码改成了这样, 以为可以轻松解决问题。但是事实上发现毫无用处,界面还是原来一样的行为,点击按钮之后卡住了几秒。
- $("#submit").on("click", function() {
- setTimeout(function() {
- bigTask()
- }, 0)
- })
这个问题我百思不得其解,最后多方查阅资料才明白了如下的内容:
- 浏览器中的JS是单线程的。
- setInterval和setTimeout并不是多线程,这两个函数根本上其实是事件触发函数
想证明setInterval和setTimeout不是多线程很简单,你可以试试这样一段代码
- setTimeout(function() {
- while(true){}
- }, 0)
- setTimeout(function() {
- alert("foo")
- }, 1000)
不出意外,你的浏览器无法响应了,页面上面的按钮不能点,Gif也不能动,那个alert肯定也出不来。这是为什么呢?
为了解释上面的问题,我们来深入解析一下浏览器。浏览器中有三个常驻的线程,分别是JS引擎,界面渲染,事件响应。由于这三个线程同时要访问DOM树,所以为了线程安全,浏览器内部需要做互斥:当JS引擎在执行代码的时候,界面渲染和事件响应两个线程是被暂停的。所以当JS出现死循环,浏览器无法响应点击,也无法更新界面。现在的浏览器的JS引擎都是单线程的,尽管多线程功能强大,但是线程同步比较复杂,并且危险,稍有不慎就会崩溃死锁。单线程的好处不必考虑线程同步这样的复杂问题,简单而安全。下面的一幅图来简要说明JS引擎的执行流程:
JS引擎基于事件来执行代码。事件响应线程在接到事件后,把响应的代码放到JS引擎的队列中,JS引擎按顺序执行代码。在JS引擎没有代码可以执行的时候,比如图中蓝色方框的间隙中,事件线程和渲染线程得以有机会运行。基于这些信息,能够的出下面的结论
- setTimeout,setInterval并不是多线程,只是一个定时的事件触发器,它们在合适的时间把一些JS代码塞到JS引擎的队列中。
- setTimeout(aFunction, 0),这行代码看似的意思是在0秒之后执行aFunction, 但这并不意味着立即执行。其它真正的意思是立刻把aFunction的代码放到当前JS引擎的队列中。所以当前代码块执行完成之前,aFunction的代码是得不到执行的。比如这段代码,一定是world先出来,hello后出来。尽管setTimeout的参数是0,但这并不意味着立即执行
- setTimeout(function() {
- alert("hello");
- }, 0)
- alert("world")
- 在一个事件的响应代码执行完成之后,即使队列中有待执行的代码,浏览器也会先执行页面渲染和响应事件,完成之后再执行队列中的代码。
异步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
总结
结合上面的分析,总结出出下面的一些实践供各位参考。
- 避免编写计算密集的前端代码。
- 使用异步Ajax。
- 避免编写一个需要较长时间来执行的JS代码,比如生成一个大型的表。遇到这种情况,可以分批执行,比如用setInterval来每秒生成20行,或是用户向下拖动滚动条时候再继续产生新的行。
- 在页面初始化时候不要执行很多的初始化代码,否则会影响页面渲染变慢。一些不需要立即执行的代码可以在页面渲染完成之后再执行,比如绑定事件,生成菜单之类的控件。
- 对于复杂页面(像淘宝首页),可以结合异步Ajax分批产生页面。先生成页面框架,页面内容自上而下用异步Ajax逐步加载并填充到框架中。这样能够让用户更早的看到页面。
- setTimeout(function, 0)是有用的。它可以让callback作为另外一个事件响应代码来执行。实现了当前事件的代码执行完成之后,再渲染DOM,再执行setTimeout的callback。这样能够让一部分代码延后执行,并且在这之前渲染DOM。
JavaScript 进阶(一)JS的"多线程"的更多相关文章
- JavaScript进阶(九)JS实现本地文件上传至阿里云服务器
JS实现本地文件上传至阿里云服务器 前言 在前面的博客< JavaScript进阶(八)JS实现图片预览并导入服务器功能>(点击查看详情)中,实现了JS将本地图片文件预览并上传至阿里云服务 ...
- JavaScript进阶(八)JS实现图片预览并导入服务器功能
JS实现导入文件功能 赠人玫瑰,手留余香.若您感觉此篇博文对您有用,请花费2秒时间点个赞,您的鼓励是我不断前进的动力,共勉!(PS:此篇博文是自己在午饭时间所写,为此没吃午饭,这就是程序猿 ...
- 二、JavaScript语言--JS基础--JavaScript进阶篇--JS基础语法
1.变量 定义:从字面上看,变量是可变的量:从编程角度讲,变量是用于存储某种/某些数值的存储器.我们可以把变量看做一个盒子,盒子用来存放物品,物品可以是衣服.玩具.水果...等. 命名:变量名字可以任 ...
- Javascript进阶篇——(JS基础语法)笔记整理
根据慕课网学习整理到一起的笔记,把东西整理到一起看起来比较方便 什么是变量字面意思:变量是可变的量:编程角度:变量是用于存储某种/某些数值的存储器.我们可以把变量看做一个盒子,盒子用来存放物品,物品可 ...
- JavaScript进阶(七)JS截取字符串substr 和 substring方法的区别
JS截取字符串substr 和 substring方法的区别 substr方法 返回一个从指定位置开始的指定长度的子字符串. stringvar.substr(start [, length ]) 参 ...
- JavaScript进阶(五)js中取小数整数部分函数
js中取小数整数部分函数 丢弃小数部分,保留整数部分 js:parseInt(7/2) 向上取整,有小数就整数部分加1 js: Math.ceil(7/2) 四舍五入 js: Math.round(7 ...
- JavaScript进阶(四)js字符串转换成数字的三种方法
js字符串转换成数字的三种方法 在js读取文本框或者其它表单数据的时候获得的值是字符串类型的,例如两个文本框a和b,如果获得a的value值为11,b的value值为9 ,那么a.value要小于b. ...
- JavaScript进阶(二)在一个JS文件中引用另一个JS文件
在一个JS文件中引用另一个JS文件 转载地址:http://blog.csdn.net/zndxlxm/article/details/7875787 方法一 在调用文件的顶部加入下例代码 ...
- web Worker使js实现‘多线程’?
大家都知道js是单线程的,在上一段js执行结束之前,后面的js绝对不会执行,那么为什么标题说js实现‘多线程’,虽然说加了引号,可是标题也不能乱写不是,可恶的标题党? 姑且抛开标题不说,先说我们经常会 ...
随机推荐
- (Problem 17)Number letter counts
If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + ...
- HDU ACM 1066 Last non-zero Digit in N!
#include<iostream> using namespace std; int mod[20]={1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2}; ...
- Oracle 11gR2的完全卸载
首先停止oracle服务,卸载oracle,其次删除oracle文件夹,最后删除oracle服务和清理注册表. 以下是详细教程 1.关闭oracle所有的服务.可以在windows的服务管理器中关闭: ...
- 利用ant的javac任务来编译java程序
<?xml version="1.0" encoding="UTF-8"?> <project name="javaTest&quo ...
- Python 第五篇(下):系统标准模块(shutil、logging、shelve、configparser、subprocess、xml、yaml、自定义模块)
目录: shutil logging模块 shelve configparser subprocess xml处理 yaml处理 自定义模块 一,系统标准模块: 1.shutil:是一种高层次的文件操 ...
- centos安装python gcc sqlite
终端中输入命令:yum install gcc -y yum install python -y yum install sqlite -y
- 线程:Message和Runnable
原文地址http://blog.csdn.net/flowingflying/article/details/6370184 程序需要相应用户的操作,最要能在200ms(0.2s)之内,如果超过5秒没 ...
- 设计模式(三)建造者模式Builder(创建型)
1. 概述 在软件开发的过程中,当遇到一个“复杂的对象”的创建工作,该对象由一定各个部分的子对象用一定的算法构成,由于需求的变化,复杂对象的各个部分经常面临剧烈的变化,但将它们组合在一起的算法相对稳定 ...
- POJ 3986 Math teacher's homework
题目 给出\(n,m_1,m_2,...,m_n\),求\(x_1 xor x_2 xor ... xor x_n=k (0 \leq x_i \leq m_i)\)的解的数量.二进制位数小于\(32 ...
- Swift实现糗事百科Demo(实战项目)
在这里,你将会学习到解析JSON数据,网络请求功能,动态调整cell内容等功能!!! 最终的结果 是这样的,项目相对简单,很适合入门!下面让我们一起开始教程之旅吧! 1.先看下项目工程结构: 第一步: ...