Web前端性能优化——编写高效的JavaScript
前言
随着计算机的发展,Web富应用时代的到来,Web 2.0早已不再是用div+css高质量还原设计的时代。自Gmail网页版邮件服务的问世开始,Web前端开发也开启了新的纪元。用户需求不断提高,各种新的技术层出不穷,前端工程师的地位也越来越重要。然而任何事物都是有两面性的,随着前端技术的发展,前端业务越来越繁重,这大大增加了JS代码量。因此,要提高Web的性能,我们不仅需要关注页面加载的时间,还要注重在页面上操作的响应速度。那么,接下来我们讨论几种能够提高JavaScript效率的方法。
一、从JavaScript的作用域谈起
当JavaScript代码执行时,JavaScript引擎会创建一个执行环境,又叫执行上下文。执行环境定义了变量或函数有权访问的其他数据,决定了它们的行为,每个执行环境都有一个与它关联的变量对象,环境中定义的所有函数、变量都保存在这个对象中。在页面加载的时候,JavaScript引擎会创建一个全局的执行环境,所有全局变量和函数都是作为window对象(浏览器中)的属性和方法创建的。在此之后,每执行一个函数,JavaScript引擎都会创建一个对应的执行环境,并将该环境放入环境栈中,所以当前正在执行的函数的执行环境是在环境栈的最顶部的,当函数执行完毕之后,其执行环境会弹出栈,并被销毁,保存在其中的变量和函数定义也会被销毁。
当代码在一个执行环境中执行时,JavaScript引擎会创建变量对象的一个作用域链,它可以保证对执行环境有权访问的变量和函数的有序访问。作用域链的前端始终是当前执行的代码所在的环境的变量对象。全局环境的作用域链中只有一个变量对象,它定义了所有可用的全局变量和函数。当函数被创建时,JavaScript引擎会把创建时执行环境的作用域链赋给函数的内部属性[[scope]];当函数被执行时,JavaScript引擎会创建一个活动对象,最开始时这个活动对象只有一个变量,即arguments对象。该活动对象会出现在执行环境作用域链的顶端,接下来是函数[[scope]]属性中的对象。
当需要查找某个变量或函数时,JavaScript引擎会通过执行环境的作用域链来查找变量和函数,从作用域链的顶端开始,如果没找到,则向下寻找直至找到为止。若一直到全局作用域都没有找到,则该变量或函数为undefined。
举个栗子:
function add(a,b) {
return a + b;
}
var result = add(2,3);
代码执行时,add函数有一个仅包含全局变量对象的[[scope]]属性,add函数执行时,JavaScript引擎创建新的执行环境以及一个包含this、arguments、a、b的活动对象,并将其添加到作用域链中。如下图所示:

二、使用局部变量
了解了作用域链的概念,我们应该知道在查找变量会从作用域链的顶端开始一层一层的向下找。显然,查找的层数越多,花费的时间越多。所以为了提高查找的速度,我们应该尽量使用 局部变量(到目前为止,局部变量是JavaScript中读写最快的标识符)。
例如:
function createEle() {
document.createElement("div");
}
function createEle() {
var doc = document;
doc.createElement("div");
}
当document使用次数比较少时,可能无所谓,可是如果在一个函数的循环中大量使用document,我们可以提前将document变成局部变量。
来看看jquery怎么写的:
(function(window, undefined) {
var jQuery = function() {}
// ...
window.jQuery = window.$ = jQuery;
})(window);
这样写的优势:
1、window和undefined都是为了减少变量查找所经过的scope作用域。当window通过传递给闭包内部之后,在闭包内部使用它的时候,可以把它当成一个局部变量,显然比原先在window scope下查找的时候要快一些。(原来的window处于作用域链的最顶端,查找速度慢)
2、在jquery压缩版本jquery.min.js中可以将局部变量window替换成单个字母,减小文件大小,提高加载速度
。
3、undefined也是JavaScript中的全局属性。将undefined作为参数传递给闭包,因为没给它传递值,它的值就是undefined,这样闭包内部在使用它的时候就可以把它当做局部变量使用,从而提高查找速度。undefined并不是JavaScript的保留字或者关键字。
4、undefined在某些低版本的浏览器(例如IE8、IE7)中值是可以被修改的(在ECMAScript3中,undefined是可读/写的变量,可以给它赋任意值,这个错误在ECMAScript5中做了修正),将undefined作为参数并且不给它传值可以防止因undefined的值被修改而产生的错误。
三、避免增长作用域链
在JavaScript中,有两种语句可以临时增加作用域链:with、try-catch
with可以使对象的属性可以像全局变量来使用,它实际上是将一个新的变量对象添加到执行环境作用域的顶部,这个变量对象包含了指定对象的所有属性,因此可以直接访问。
这样看似很方便,但是增长了作用域链,原来函数中的局部变量不在处于作用域链的顶端,因此在访问这些变量的时候要查找到第二层才能找到它。当with语句块之行结束后,作用域链将回到原来的状态。鉴于with的这个缺点,所以不推荐使用。
try-catch中的catch从句和with类似,也是在作用域链的顶端增加了一个对象,该对象包含了由catch指定命名的异常对象。但是因为catch语句只有在放生错误的时候才执行,因此影响比较少。
四、字符串链接优化
由于字符串是不可变的,所以在进行字符串连接时,需要创建临时字符串。频繁创建、销毁临时字符串会导致性能低下。
当然,这个问题在新版本浏览器包括IE8+中都得到了优化,所以不需要担心
在低版本浏览器(IE6、IE7)中,我们可以种数组的join方法来代替。
var temp = [];
var i = 0;
temp[i++] = "Hello";
temp[i++] = " ";
temp[i++] ="everyone"; var outcome = temp.join("");
五、条件判断
当出现条件判断时,我们采用什么样的结构才能使性能最优?
if(val == 0) {
return v0;
}else if(val == 1) {
return v1;
}else if(val == 2) {
return v2;
}else if(val == 3) {
return v3;
}else if(val == 4) {
return v4;
}
当条件分支比较多时,我们可以斟酌哪种条件出现的概率比较大,并将对应的语句放在最上面,这样可以减少判断次数。
使用switch语句,新版的浏览器基本上都对switch做了优化,这样层数比较深时,性能比if会更好
使用数组:
var v = [v0,v1,v2,v3,v4];
return v[valeue];
要求:对应的结果是单一值,而不是一系列操作
另外,其他方面的优化,譬如
if(condition1) {
return v1;
}
else {
return v2
}
// 改成
if(condition1) {
return v1;
}
return v2;
六、快速循环
1、循环总次数使用局部变量
for( var i = 0;i < arr.length;i++) {
}
// 改成
var len = arr.length;
for( var i = 0;i < len;i++) {
}
这样就避免了每次循环的属性查找。这点尤其重要,因为在进行dom操作时,很多人会这样写:
var divList = document.getElementsByTagName("div");
for( var i = 0;i < divList.length;i++) {
}
查找DOM元素的属性是相对耗时的,所以应该避免这种写法。
2、如果可以,递减代替递增
for(var i = 0;i < arr.length;i++) {
}
// 改成
for(var i = arr.length - 1;i--;) {
}
var i = 0;
while(i < arr.length) {
i++;
}
// 改成
var i = arr.length - 1;
while(i--) {
}
i=0的时候会直接跳出,循环次数比较多时还是很有用的。
七、展开循环
var i = arr.length - 1;
while(i--) {
dosomething(arr[i]);
}
遇到这样的情况时,执行一次循环的时候我们可以选择不止执行一次函数。
var interations = Math.floor(arr.length / 8);
var left = arr.length % 8;
var i = 0; if(left) {
do {
dosomething(arr[i++]);
} while(--left);
}
do {
dosomething(arr[i++]);
dosomething(arr[i++]);
dosomething(arr[i++]);
dosomething(arr[i++]);
dosomething(arr[i++]);
dosomething(arr[i++]);
dosomething(arr[i++]);
dosomething(arr[i++]);
} while(--interations);
当遇到大数组,减少循环的开销,性能不就提上去了嘛。(至于为什么是每次循环,调8次函数,大牛测出来的,这样达到最佳)
八、高效存取数据
JavaScript中4种地方可以存取数据:
字面量值;变量;数组元素;对象属性
字面量值和变量中存取数据是最快的,从数组元素和对象属性中存取数据相对较慢,并且随着深度增加,存取速度会越来越慢,譬如obj.item.value就比obj.item慢。
某些情况下我们可以将对象、数组属性存成局部变量来提高速度,譬如:
for( var i = 0;i < arr.length;i++) {
}
// 改成
var len = arr.length;
for( var i = 0;i < len;i++) {
}
var divList = document.getElementsByTagName("div");
for( var i = 0;i < divList.length;i++) {
}
// 改成
//
var divList = document.getElementsByTagName("div");
for( var i = 0,len = divList.length;i < len;i++) {
}
九、事件委托
事件委托就是利用冒泡的原理,将原本应该添加在某些元素身上的监听事件,添加到其父元素身上,来达到提高性能的效果。
举个栗子:
<div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
</div>
<script>
window.onload = function() {
var ul = document.getElementsByTagName('ul')[0];
var liList = document.getElementsByTagName('li'); for(var i = 0,len = liList.length;i < len;i++) {
liList[i].onclick = function() {
alert(this.innerHTML);
}
}
}
</script>
这样我们就为每个li添加了监听事件了。
显然,我们通过循环为每个li添加监听事件是不优化的。这样不仅浪费了内存,在新的li加入的时候我们还要重新为它添加监听事件。
我们可以这样写:
<div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
</div>
<script>
window.onload = function() {
var ul = document.getElementsByTagName('ul')[0];
var liList = document.getElementsByTagName('li'); ul.onclick = function(e) {
var e = e || window.event;
var target = e.target || e.srcElement; if(target.nodeName.toLowerCase() == "li") {
alert(target.innerHTML);
}
}
}
</script>
这样写的好处:
只添加一个监听事件,节省了内存;新加入li的时候我们也不用为它单独添加监听事件;在页面中添加事件处理程序所需的时候更少,因为我们只需要为一个DOM元素添加事件处理程序。
如果是原创文章,转载请注明出处:http://www.cnblogs.com/MarcoHan/
最后,提一点不会提高性能的建议
if( 2 == value) {
}
类似这种判断的时候推荐常量放在左边,这样就可以预防类似 if( value = 2){} 的错误了,因为如果少写了一个等号, if( value = 2) {} 是合法的语句,而且代码量变大的时候不容易检查出来。if( 2 = value) {} 这样少写了等号JavaScript引擎会直接报错,我们就可以愉快地改过来了。(只是建议)
Web前端性能优化——编写高效的JavaScript的更多相关文章
- Web前端性能优化进阶——完结篇
前言 在之前的文章 如何优化网站性能,提高页面加载速度 中,我们简单介绍了网站性能优化的重要性以及几种网站性能优化的方法(没有看过的可以狂戳 链接 移步过去看一下),那么今天我们深入讨论如何进一步优化 ...
- Web前端性能优化的9大问题
1.请减少HTTP请求基本原理:在浏览器(客户端)和服务器发生通信时,就已经消耗了大量的时间,尤其是在网络情况比较糟糕的时候,这个问题尤其的突出.一个正常HTTP请求的流程简述:如在浏览器中输入&qu ...
- web前端性能优化的技巧
1. 请减少HTTP请求 基本原理: 在浏览器(客户端)和服务器发生通信时,就已经消耗了大量的时间,尤其是在网络情况比较糟糕的时候,这个问题尤其的突出. 一个正常HTTP请求的流程简述:如在浏览器中输 ...
- web前端性能优化指南(转)
web前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loading4. 基于联通3G网络 ...
- Web前端性能优化教程09:图像和Cookie优化
本文是Web前端性能优化系列文章中的第九篇,主要讲述内容:图像和Cookie优化.完整教程可查看: 一. 图像优化 图像基础知识 gif: 适用于动画效果,例如提示的滚动条图案 jpg: 是一种使用 ...
- Web前端性能优化教程05:网站样式和脚本
本文是Web前端性能优化系列文章中的第五篇,主要讲述内容:网站样式和脚本代码的放置位置.使用外部javascript和css.完整教程可查看:Web前端性能优化 一.将样式表放在顶部 可视性回馈的重要 ...
- Web前端性能优化教程07:精简JS 移除重复脚本
本文是Web前端性能优化系列文章中的第七篇,主要讲述内容:精简Javascript代码,以及移出重复脚本.完整教程可查看: 一.精简javascript 基础知识 精简:从javascript代码中 ...
- web前端性能优化指南
web前端性能优化指南 web前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loadin ...
- 网站的高性能架构---Web前端性能优化
网站性能测试 不同视角下的网站性能 用户视角的网站性能:从用户角度,网站性能就是用户在浏览器上直观感受到的网站响应速度.用户的感受时间包括用户计算机和网站服务器通信的时间.网站服务器处理请求时间.用户 ...
随机推荐
- SIFT feature
转载:http://www.cnblogs.com/wangguchangqing/p/4853263.html 1.SIFT概述 SIFT的全称是Scale Invariant Feature Tr ...
- UML和模式应用3:迭代和进化式分析和设计案例研究
1.前言 如何进行迭代和进化式分析和设计?将采用案例研究的方式贯穿始终.案例研究所包含的内容: UI元素 核心应用逻辑层 数据库访问 与外部软硬构件的协作 本章关于OOA/D主要介绍核心应用逻辑层 2 ...
- /etc/fstab文件详解【转】
******************************************************************************* 有很多人经常修改/etc/fstab文件 ...
- Linux命令:pigz多线程压缩工具【转】
学习Linux系统时都会学习这么几个压缩工具:gzip.bzip2.zip.xz,以及相关的解压工具.关于这几个工具的使用和相互之间的压缩比以及压缩时间对比可以看:Linux中归档压缩工具学习 那么P ...
- 调用链系列三、基于zipkin调用链封装starter实现springmvc、dubbo、restTemplate等实现全链路跟踪
一.实现思路 1.过滤器实现思路 所有调用链数据都通过过滤器实现埋点并收集.同一条链共享一个traceId.每个节点有唯一的spanId. 2.共享传递方式 1.rpc调用:通过隐式传参.dubbo有 ...
- Project Euler Problem6
Sum square difference Problem 6 The sum of the squares of the first ten natural numbers is, 12 + 22 ...
- 通达OA在centos系统中快速部署文档(web和数据库)
通达OA2008从windows环境移植到linux中(centos5.5及以上版本) 如果安装好了,还是无法访问,则需要清空浏览器缓存即可 1.安装lamp环境,这里用的是xampp集成安装包xam ...
- centos系统初始化流程及实现系统裁剪
Linux系统的初始化流程: POST:ROM+RAM BIOS: Boot Sequence MBR: 446:bootloader 64: 分区表 2: 5A kernel文件:基本磁盘分区 /s ...
- Python-CSS高级 题目
一.简答1.完整总结display三种基础显示方式的显示方式与嵌套规则 /* inline */ /*1.同行显示, 就相当于纯文本, 当一行显示不下, 如就是一个字显示不下,那么显示不下的那一个字就 ...
- 浏览器桌面通知Notification实践
一言不合就上图: 最近常常在浏览器看到这样的消息推送,还有QQ.com的推送,现在我对这个不了解,不知道叫消息自动推送对不对,这个时chrome浏览器的截图,出现在右下角,其他浏览器的样式可能有些微差 ...