简介: JavaScript 是一个比较完善的前端开发语言,在现今的 web 开发中应用很是普遍,尤为是对 Web 2.0 的应用。随着 Web 2.0 愈来愈流行的今天,咱们会发现:在咱们的 web 应用项目中,会有大量的 JavaScript 代码,而且之后会愈来愈多。JavaScript 做为一个解释执行的语言,以及它的单线程机制,决定了性能问题是 JavaScript 的软肋,也是 web 软件工程师们在写 JavaScript 须要高度重视的一个问题,尤为是针对 Web 2.0 的应用。绝大多数 web 软件工程师都或多或少的遇到过所开发的 Web 2.0 应用的性能欠佳的问题,其主要缘由就是 JavaScript 性能不足,浏览器负荷太重。可是,解决这种解释执行而且单线程运做语言的性能问题也并不是易事。这篇文章会着重介绍一些关于开发中 JavaScript 性能调优的技巧和最佳实践,一样也会涉及到关于 JavaScript 操做 DOM 节点的性能调优的一些方法 .javascript
本文的标签: javascript, 优化, 性能, 性能调优css
简介html
Web 开发中常常会遇到性能的问题,尤为是针对当今的 Web2.0 应用。JavaScript 是当今使用最为普遍的 Web 开发语言,Web 应用的性能问题很大一部分都是由程序员写的 JavaScript 脚本性能不佳所形成的,里面包括了 JavaScript 语言自己的性能问题,以及其与 DOM 交互时的性能问题。本文主要来探讨一下如何尽量多的避免这类问题,从而最大限度的提升 Web 应用的性能。前端
回页首html5
JavaScript 性能调优java
JavaScript 语言因为它的单线程和解释执行的两个特色,决定了它自己有不少地方有性能问题,因此可改进的地方有很多。程序员
eval 的问题:web
比较下述代码:ajax
清单 1. eval 的问题
正则表达式
var reference = {}, props = “p1”; eval(“reference.” + props + “=5”) var reference = {}, props = “p1”; reference[props] = 5 |
有“eval”的代码比没有“eval”的代码要慢上 100 倍以上。
主要缘由是:JavaScript 代码在执行前会进行相似“预编译”的操做:首先会建立一个当前执行环境下的活动对象,并将那些用 var 申明的变量设置为活动对象的属性,可是此时这些变量的赋值都是 undefined,并将那些以 function 定义的函数也添加为活动对象的属性,并且它们的值正是函数的定义。可是,若是你使用了“eval”,则“eval”中的代码(实际上为字符串)没法预先识别其上下文,没法被提早解析和优化,即没法进行预编译的操做。因此,其性能也会大幅度下降。
比较下述代码:
var func1 = new Function(“return arguments[0] + arguments[1]”); func1(10, 20); var func2 = function(){ return arguments[0] + arguments[1] }; func2(10, 20); |
这里相似以前提到的“eval”方法,这里“func1”的效率会比“func2”的效率差不少,因此推荐使用第二种方式。
JavaScript 代码解释执行,在进入函数内部时,它会预先分析当前的变量,并将这些变量纳入不一样的层级(level),通常状况下:
局部变量放入层级 1(浅),全局变量放入层级 2(深)。若是进入“with”或“try – catch”代码块,则会增长新的层级,即将“with”或“catch”里的变量放入最浅层(层 1),并将以前的层级依次加深。
参考以下代码:
var myObj = … .. … .. function process(){ var images = document.getElementsByTagName("img"), widget = document.getElementsByTagName("input"), combination = []; for(var i = 0; i < images.length; i++){ combination.push(combine(images[i], widget[2*i])); } myObj.container.property1 = combination[0]; myObj.container.property2 = combination[combination.length-1]; } |
这里咱们能够看到,“images”,“widget”,“combination”属于局部变量,在层 1。“document”,“myObj”属于全局变量,在层 2。
变量所在的层越浅,访问(读取或修改)速度越快,层越深,访问速度越慢。因此这里对“images”,“widget”,“combination”的访问速度比“document”,“myObj”要快一些。因此推荐尽可能使用局部变量,可见以下代码:
var myObj = … .. … .. function process(){ var doc = document; var images = doc.getElementsByTagName("img"), widget = doc.getElementsByTagName("input"), combination = []; for(var i = 0; i < images.length; i++){ combination.push(combine(images[i], widget[2*i])); } myObj.container.property1 = combination[0]; myObj.container.property2 = combination[combination.length-1]; } |
咱们用局部变量“doc”取代全局变量“document”,这样能够改进性能,尤为是对于大量使用全局变量的函数里面。
再看以下代码:
var myObj = … .. … .. function process(){ var doc = document; var images = doc.getElementsByTagName("img"), widget = doc.getElementsByTagName("input"), combination = []; for(var i = 0; i < images.length; i++){ combination.push(combine(images[i], widget[2*i])); } with (myObj.container) { property1 = combination[0]; property2 = combination[combination.length-1]; } } |
加上“with”关键字,咱们让代码更加简洁清晰了,可是这样作性能会受影响。正如以前说的,当咱们进入“with”代码块时,“combination”便从原来的层 1 变到了层 2,这样,效率会大打折扣。因此比较一下,仍是使用原来的代码:
var myObj = … .. … .. function process(){ var doc = document; var images = doc.getElementsByTagName("img"), widget = doc.getElementsByTagName("input"), combination = []; for(var i = 0; i < images.length; i++){ combination.push(combine(images[i], widget[2*i])); } myObj.container.property1 = combination[0]; myObj.container.property2 = combination[combination.length-1]; } |
可是这样并非最好的方式,JavaScript 有个特色,对于 object 对象来讲,其属性访问层级越深,效率越低,好比这里的“myObj”已经访问到了第 3 层,咱们能够这样改进一下:
var myObj = … .. … .. function process(){ var doc = document; var images = doc.getElementsByTagName("img"), widget = doc.getElementsByTagName("input"), combination = []; for(var i = 0; i < images.length; i++){ combination.push(combine(images[i], widget[2*i])); } var ctn = myObj.container; ctn.property1 = combination[0]; ctn.property2 = combination[combination.length-1]; } |
咱们用局部变量来代替“myObj”的第 2 层的“container”对象。若是有大量的这种对对象深层属性的访问,能够参照以上方式提升性能。
字符串拼接
常常看到这样的代码:
str += “str1” + “str2” |
这是咱们拼接字符串经常使用的方式,可是这种方式会有一些临时变量的建立和销毁,影响性能,因此推荐使用以下方式拼接:
var str_array = []; str_array.push(“str1”); str_array.push(“str2”); str = str_array.join(“”); |
这里咱们利用数组(array)的“join”方法实现字符串的拼接,尤为是程序的老版本的 Internet Explore(IE6)上运行时,会有很是明显的性能上的改进。
固然,最新的浏览器(如火狐 Firefox3+,IE8+ 等等)对字符串的拼接作了优化,咱们也能够这样写:
str +=“str1” str +=“str2” |
新的浏览器对“+=”作了优化,性能略快于数组的“join”方法。在不久的未来更新版本浏览器可能对“+”也会作优化,因此那时咱们能够直接写:str += “str1” + “str2”。
隐式类型转换
参考以下代码:
var str = “12345678”, arr = []; for(var i = 0; i <= s.length; i++){ arr.push( str.charAt(i)); } |
这里咱们在每一个循环时都会调用字符串的“charAt”方法,可是因为咱们是将常量“12345678”赋值给“str”,因此“str”这里事实上并非一个字符串对象,当它每次调用“charAt”函数时,都会临时构造值为“12345678”的字符串对象,而后调用“charAt”方法,最后再释放这个字符串临时对象。咱们能够作一些改进:
var str = new Stirng(“12345678”), arr = []; for(var i = 0; i <= s.length; i++){ arr.push( str.charAt(i)); } |
这样一来,变量“str”做为一个字符串对象,就不会有这种隐式类型转换的过程了,这样一来,效率会显著提升。
字符串匹配
JavaScript 有 RegExp 对象,支持对字符串的正则表达式匹配。是一个很好的工具,可是它的性能并非很是理想。相反,字符串对象(String)自己的一些基本方法的效率是很是高的,好比“substring”,“indexOf”,“charAt”等等,在咱们须要用正则表达式匹配字符串时,能够考虑一下:
这些方式都可以有效的提升程序的效率。
关于正则表达式对象,还有一点须要注意,参考以下代码:
for(var i = 0; i <= str_array.length; i++){ if(str_array[i].match(/^s*extra\s/)){ …………………… } } |
这里,咱们往“match”方法传入“/^s*extra\s/”是会影响效率的,它会构建临时值为“/^s*extra\s/”的正则表达式对象,执行“match”方法,而后销毁临时的正则表达式对象。咱们能够这样作:
var sExpr = /^s*extra\s/; for(var i = 0; i <= str_array.length; i++){ if(str_array[i].match(sExpr)){ …………………… } } |
这样就不会有临时对象了。
setTimeout 和 setInterval
“setTimeout”和“setInterval”这两个函数能够接受字符串变量,可是会带来和以前谈到的“eval”相似的性能问题,因此建议仍是直接传入函数对象自己。
利用提早退出
参考以下两段代码:
// 代码 1 var name = … .; var source = …… ; if(source.match(/ …… /)){ …………………………… } // 代码 2 var name = … .; var source = …… ; if(name.indexOf( … ) &&source.match(/ …… /)){ …………………………… } |
代码 2 多了一个对“name.indexOf( … )”的判断,这使得程序每次走到这一段时会先执行“indexOf”的判断,再执行后面的“match”,在“indexOf”比“match”效率高不少的前提下,这样作会减小“match”的执行次数,从而必定程度的提升效率。
JavaScript 的开发离不开 DOM 的操做,因此对 DOM 操做的性能调优在 Web 开发中也是很是重要的。
Repaint 也叫 Redraw,它指的是一种不会影响当前 DOM 的结构和布局的一种重绘动做。以下动做会产生 Repaint 动做:
Reflow 比起 Repaint 来说就是一种更加显著的变化了。它主要发生在 DOM 树被操做的时候,任何改变 DOM 的结构和布局都会产生 Reflow。但一个元素的 Reflow 操做发生时,它的全部父元素和子元素都会放生 Reflow,最后 Reflow 必然会致使 Repaint 的产生。举例说明,以下动做会产生 Repaint 动做:
经过 Reflow 和 Repaint 的介绍可知,每次 Reflow 比其 Repaint 会带来更多的资源消耗,咱们应该尽可能减小 Reflow 的发生,或者将其转化为只会触发 Repaint 操做的代码。
参考以下代码:
var pDiv = document.createElement(“div”); document.body.appendChild(pDiv);----- reflow var cDiv1 = document.createElement(“div”); var cDiv2 = document.createElement(“div”); pDiv.appendChild(cDiv1);----- reflow pDiv.appendChild(cDiv2);----- reflow |
这是咱们常常接触的代码了,可是这段代码会产生 3 次 reflow。再看以下代码:
var pDiv = document.createElement(“div”); var cDiv1 = document.createElement(“div”); var cDiv2 = document.createElement(“div”); pDiv.appendChild(cDiv1); pDiv.appendChild(cDiv2); document.body.appendChild(pDiv);----- reflow |
这里便只有一次 reflow,因此咱们推荐这种 DOM 节点操做的方式。
关于上述较少 Reflow 操做的解决方案,还有一种能够参考的模式:
var pDiv = document.getElementById(“parent”); pDiv.style.display = “none”----- reflow pDiv.appendChild(cDiv1); pDiv.appendChild(cDiv2); pDiv.appendChild(cDiv3); pDiv.appendChild(cDiv4); pDiv.appendChild(cDiv5); pDiv.style.width = “100px”; pDiv.style.height = “100px”; pDiv.style.display = “block”----- reflow |
先隐藏 pDiv,再显示,这样,隐藏和显示之间的操做便不会产生任何的 Reflow,提升了效率。
DOM 元素里面有一些特殊的测量属性的访问和方法的调用,也会触发 Reflow,比较典型的就是“offsetWidth”属性和“getComputedStyle”方法。
这些测量属性和方法大体有这些:
这些属性和方法的访问和调用,都会触发 Reflow 的产生,咱们应该尽可能减小对这些属性和方法的访问和调用,参考以下代码:
var pe = document.getElementById(“pos_element”); var result = document.getElementById(“result_element”); var pOffsetWidth = pe.offsetWidth; result.children[0].style.width = pOffsetWidth; result.children[1].style.width = pOffsetWidth; result.children[2].style.width = pOffsetWidth; …………其余修改………… |
这里咱们能够用临时变量将“offsetWidth”的值缓存起来,这样就不用每次访问“offsetWidth”属性。这种方式在循环里面很是适用,能够极大地提升性能。
咱们确定常常见到以下的代码:
var sElement = document.getElementById(“pos_element”); sElement.style.border = ‘ 1px solid red ’ sElement.style.backgroundColor = ‘ silver ’ sElement.style.padding = ‘ 2px 3px ’ sElement.style.marginLeft = ‘ 5px ’ |
可是能够看到,这里的每个样式的改变,都会产生 Reflow。须要减小这种状况的发生,咱们能够这样作:
解决方案 1:
.class1 { border: ‘ 1px solid red ’ background-color: ‘ silver ’ padding: ‘ 2px 3px ’ margin-left: ‘ 5px ’ } document.getElementById(“pos_element”).className = ‘class1’ ; |
用 class 替代 style,能够将原有的全部 Reflow 或 Repaint 的次数都缩减到一个。
解决方案 2:
var sElement = document.getElementById(“pos_element”); var newStyle = ‘ border: 1px solid red; ’ + ‘ background-color: silver; ’ + ‘ padding: 2px 3px; ’ + “margin-left: 5px;” sElement.style.cssText += newStyle; |
一次性设置全部样式,也是减小 Reflow 提升性能的方法。
一个页面上每每包含 1000 多页面元素,在定位具体元素的时候,每每须要必定的时间。若是用 id 或 name 定位可能效率不会太慢,若是用元素的一些其余属性(好比 className 等等)定位,可能效率有不理想了。有的可能只能经过遍历全部元素(getElementsByTagName)而后过滤才能找到相应元素,这就更加低效了,这里咱们推荐使用 XPath 查找元素,这是不少浏览器自己支持的功能。
if(document.evaluate){ var tblHeaders = document.evaluate(“//body/div/table//th”); var result = tblHeaders.iterateNext(); while(result) { result.style.border = “1px dotted blue”; result ……………… result = xpathResult.iterateNext(); } } else{ //getElementsByTagName() …… // 处理浏览器不支持 XPath 的状况 ……………………………… } |
浏览器 XPath 的搜索引擎会优化搜索效率,大大缩短结果返回时间。
这是一类特殊的对象,它们有点像数组,但不彻底是数组。下述方法的返回值通常都是 HTMLCollection 对象:
这些 HTMLCollection 对象并非一个固定的值,而是一个动态的结果。它们是一些比较特殊的查询的返回值,在以下状况下,它们会从新执行以前的查询而获得新的返回值(查询结果),虽然多数状况下会和前一次或几回的返回值都同样:
因此,HTMLCollection 对象对这些属性和成员的访问,比起数组来要慢不少。固然也有例外,Opera 和 Safari 对这种状况就处理的很好,不会有太大性能问题。
参考以下代码:
var items = [“test1”, “test2”, “test3”, ……………… ]; for(var i = 0; i < items.length; i++){ ……………………………… } var items = document.getElementsByTagName(“div”); for(var i = 0; i < items.length; i++){ …………………………………… . } |
上述两端代码,下面的效率比起上面一段要慢不少,由于每个循环都会有“items.length”的触发,也就会致使“document.getElementsByTagName(..)”方法的再次调用,这即是效率便会大幅度降低的缘由。咱们能够这样解决:
var items = document.getElementsByTagName(“div”); var len = items.length for(var i = 0; i < len; i++){ …………………………………… . } |
这样一来,效率基本与普通数组同样。
加载并执行一段 JavaScript 脚本是须要必定时间的,在咱们的程序中,有时候有些 JavaScript 脚本被加载后基本没有被使用过 (好比:脚本里的函数历来没有被调用等等)。加载这些脚本只会占用 CPU 时间和增长内存消耗,下降 Web 应用的性能。因此推荐动态的加载 JavaScript 脚本文件,尤为是那些内容较多,消耗资源较大的脚本文件。
if(needXHR){ document.write(“<script type= ’ test\/JavaScript ’ src= 'dojo_xhr.js' >”); } if(dojo.isIE){ document.write(“<script type= ’ test\/JavaScript ’ src= 'vml.js' >”); } |
这篇文章介绍了 Web 开发中关于性能方面须要注意的一些小细节,从 JavaScript 自己着手,介绍了 JavaScript 中须要避免的一些函数的使用和编程规则,好比 eval 的弊端,function scope chain 以及 String 的用法等等,也分享了一些比较推荐的作法,并扩展到 JavaScript 对 DOM 操做的性能调优,好比利用 Repaint 和 Reflow 的机制,如何使用特殊测量属性,样式相关的性能调优以及 HTMLCollection 对象的原理和使用小技巧。这些小细节咱们能够在开发过程当中尽可能注意一下,以尽量多的提升咱们 Web 应用的性能。
学习
讨论