前端网页与js性能优化

时间:2021-01-28 21:12:30

前端网页性能最佳实践

你愿意为打开一个网页等待多长时间?我一秒也不愿意等。但是事实上大多数网站在响应速度方面都让人失望。现在越来越多的人开始建立自己的网站,博客,你的网页响应速度如何呢?在这篇文章中我们来介绍一下提高网页性能的最佳实践,以及相应的问题解决方案,让站长或者即将要成为站长的朋友了解如何去测试和提高网站响应速度,对自己的网站更有信心。

最佳实践

最佳实践我们引用的来自yahoo前端性能团队总结的35条黄金定律。原文猛击这里。下面我们分门别类将每条的关键点总结一下。

网页内容

减少http请求次数

减少DNS查询次数

避免页面跳转

缓存Ajax

延迟加载

提前加载

减少DOM元素数量

根据域名划分内容

减少iframe数量

避免404

 服务器

使用CDN

添加Expires 或Cache-Control报文头

Gzip压缩传输文件

配置ETags

尽早flush输出

使用GET Ajax请求

避免空的图片src

 Cookie

减少Cookie大小

页面内容使用无cookie域名

CSS

将样式表置顶

避免CSS表达式

用<link>代替@import

避免使用Filters

Javascript

将脚本置底

使用外部Javascirpt和CSS文件

精简Javascript和CSS

去除重复脚本

减少DOM访问

使用智能事件处理

 图片

优化图像

优化CSS Sprite

不要在HTML中缩放图片

使用小且可缓存的favicon.ico

移动客户端

保持单个内容小于25KB

打包组建成符合文档

网页内容

减少http请求次数

80%的响应时间花在下载网页内容(images, stylesheets, javascripts, scripts, flash等)。减少请求次数是缩短响应时间的关键!可以通过简化页面设计来减少请求次数,但页面内容较多可以采用以下技巧。

1. 捆绑文件: 现在有很多现成的库可以帮你将多个脚本文件捆绑成一个文件,将多个样式表文件捆绑成一个文件,以此来减少文件的下载次数。例如在asp.net中可以使用ScriptManager,asp.net MVC中的Bundling

2. CSS Sprites: 就是把多个图片拼成一副图片,然后通过CSS来控制在什么地方具体显示这整张图片的什么位置。给大家看个熟悉的Sprites实例。

前端网页与js性能优化

豆瓣把他的图标集中在一起,然后我们看他如何控制只显示第一个图标的

前端网页与js性能优化
.app-icon-read {
background-position
: 0 0;
}
.app-icon
{
background
: url("/pics/app/app_icons_50_5.jpg") no-repeat scroll 0 0 transparent;

border-radius
: 10px 10px 10px 10px;
box-shadow
: 1px 1px 2px #999999;
display
: inline-block;
height
: 50px;

width
: 50px;
}
前端网页与js性能优化

3. Image Maps: 也是将多幅图拼在一起,然后通过坐标来控制显示导航。这里有个经典的例子,选中图片中的某个人就会将你带到不同的链接。

Dr Johnson - Dictionary writer Boswell - Biographer Sir Joshua Reynolds - Host David Garrick - actor Edmund Burke - statesman Pasqual Paoli - Corsican patriot Charles Burney - music historian Thomas Warton - poet laureate Oliver Goldsmith - writer prob.The Infant Academy 1782 unknown painting An unknown portrait servant - poss. Francis Barber Use button to enlarge or use hyperlinks前端网页与js性能优化前端网页与js性能优化

4. Inline images: 通过编码的字符串将图片内嵌到网页文本中。例如下面的inline image的显示效果为一个勾选的checkbox。

前端网页与js性能优化
.sample-inline-png {
padding-left
: 20px;
background
: white url('data:image/png;base64,iVBORw0KGgoAA
AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC') no-repeat scroll left top;
}
前端网页与js性能优化

图片显示效果如左图

减少DNS查询次数

DNS查询也消耗响应时间,如果我们的网页内容来自各个不同的domain (比如嵌入了开放广告,引用了外部图片或脚本),那么客户端首次解析这些domain也需要消耗一定的时间。DNS查询结果缓存在本地系统和浏览器中一段时间,所以DNS查询一般是对首次访问响应速度有所影响。下面是我清空本地dns后访问博客园主页dns的查询请求。看少去还不少哦。

前端网页与js性能优化

避免页面跳转

当客户端收到服务器的跳转回复时,客户端再次根据服务器回复中的location指定的地址再次发送请求,例如以下跳转回复。

      HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html
当客户端遇到这种回复的时候,用户只能等待客户端再次发送请求,有的网站甚至会一直跳n次,跳到他想带你去的地方…当然在这个时候用户看不到任何页面内容,只有浏览器的进度条一直在刷新。

缓存Ajax

Ajax可以帮助我们异步的下载网页内容,但是有些网页内容即使是异步的,用户还是在等待它的返回结果,例如ajax的返回是用户联系人的下拉列表。所以我们还是要注意尽量应用以下规则提高ajax的响应速度。

  • 添加Expires 或 Cache-Control报文头使回复可以被客户端缓存
  • 压缩回复内容
  • 减少dns查询
  • 精简javascript
  • 避免跳转
  • 配置Etags

延迟加载

这里讨论延迟加载需要我们知道我们的网页最初加载需要的最小内容集是什么。剩下的内容就可以推到延迟加载的集合中。

Javascript是典型的可以延迟加载内容。一个比较激进的做法是开发网页时先确保网页在没有Javascript的时候也可以基本工作,然后通过延迟加载脚本来完成一些高级的功能。

提前加载

与延迟加载目的相反,提前加载的是为了提前加载接下来网页中访问的资源,下面是提前加载的类型

无条件提前加载:当前网页加载完成后,马上去下载一些其他的内容。例如google会在页面加载成功之后马上去下载一个所有结果中会用到的image sprite。

前端网页与js性能优化

有条件加载:根据用户的输入推断需要加载的内容,雅虎的示例是search.yahoo.com

前端网页与js性能优化

有预期的的加载:这种情况一般发生在网页重新设计时,由于用户经常访问旧网页,本地对旧的网页内容缓存充分从而显得旧网页速度很快,而新的网页内容却没有缓存,设计者可以在旧网页的内容中预先加载一些新网页中可能用到的内容,这样新的网页就会生下来一些需要下载的资源。

减少DOM元素数量

网页中元素过多对网页的加载和脚本的执行都是沉重的负担,500个元素和5000个元素在加载速度上会有很大差别。

想知道你的网页中有多少元素,通过在浏览器中的一条简单命令就可以算出,

document.getElementsByTagName('*').length

多少算是多了呢?雅虎在写这篇文章的时候号称主页只有700多元素,但现在接近多了一倍。我们的网页至少别比雅虎还多吧。。。

前端网页与js性能优化

根据域名划分内容

浏览器一般对同一个域的下载连接数有所限制,按照域名划分下载内容可以浏览器增大并行下载连接,但是注意控制域名使用在2-4个之间,不然dns查询也是个问题。

一般网站规划会将静态资源放在类似于static.example.com,动态内容放在www.example.com上。这样做还有一个好处是可以在静态的域名上避免使用cookie。后面我们会在cookie的规则中提到。   

减少iframe数量

使用iframe要注意理解iframe的优缺点

优点

  • 可以用来加载速度较慢的内容,例如广告。
  • 安全沙箱保护。浏览器会对iframe中的内容进行安全控制。
  • 脚本可以并行下载

缺点

  • 即使iframe内容为空也消耗加载时间
  • 会阻止页面加载
  • 没有语义

避免404

404我们都不陌生,代表服务器没有找到资源,我们要特别要注意404的情况不要在我们提供的网页资源上,客户端发送一个请求但是服务器却返回一个无用的结果,时间浪费掉了。

更糟糕的是我们网页中需要加载一个外部脚本,结果返回一个404,不仅阻塞了其他脚本下载,下载回来的内容(404)客户端还会将其当成Javascript去解析。

服务器

使用CDN

再次强调第一条黄金定律,减少网页内容的下载时间。提高下载速度还可以通过CDN(内容分发网络)来提升。CDN通过部署在不同地区的服务器来提高客户的下载速度。如果你的网站上有大量的静态内容,世界各地的用户都在访问,我说的是youtube么?那CDN是必不可少的。事实上大多数互联网中的巨头们都有自己的CDN。我们自己的网站可以先通过免费的CDN供应商来分发网页资源。

添加Expires 或Cache-Control报文头

这条规则分为两个方面,

Gzip压缩传输文件

Gzip通常可以减少70%网页内容的大小,包括脚本、样式表、图片等文件。Gzip比deflate更高效,主流服务器都有相应的压缩支持模块。

IIS中内建了静态压缩和动态压缩模块,如何配制可以参考Enable HTTP Compression of Static Content (IIS 7)Enable HTTP Compression of Dynamic Content (IIS 7)

值得注意的是pdf文件可以从需要被压缩的类型中剔除,因为pdf文件本身已经压缩,gzip对其效果不大,而且会浪费CPU。

配置ETags

虽然标题叫配制ETags,但是这里你要根据具体情况进行一些判断。首先Etag简单来说是通过一个文件版本标识使得服务器可以轻松判断该请求的内容是否有所更新,如果没有就回复304 (not modified),从而避免下载整个文件。

但是Etags的版本信息即使主流服务器未能很好地支持跨服务器的判断,比如你从一个服务器集群中一台得到Etags,然后发送到了另一台那么校验很有可能会失败。

如果你遇到这样的问题,IIS 7中可以通过如下方法将Etag去掉,使用URL Rewrite,然后在web.config中添加如下配制

前端网页与js性能优化
<rewrite>
<outboundRules>
<rule name="Remove ETag">
<match serverVariable="RESPONSE_ETag" pattern=".+" />
<action type="Rewrite" value="" />
</rule>
</outboundRules>
</rewrite>
前端网页与js性能优化

IIS8里提供了一个简单配制来直接关闭Etag,

前端网页与js性能优化
<element name="clientCache">
<attribute name="cacheControlMode" type="enum" defaultValue="NoControl">
<enum name="NoControl" value="0" />
<enum name="DisableCache" value="1" />
<enum name="UseMaxAge" value="2" />
<enum name="UseExpires" value="3" />
</attribute>
<attribute name="cacheControlMaxAge" type="timeSpan" defaultValue="1.00:00:00" />
<attribute name="httpExpires" type="string" />
<attribute name="cacheControlCustom" type="string" />
<attribute name="setEtag" type="bool" defaultValue="false" />
</element>
前端网页与js性能优化

尽早flush输出

网页后台程序中我们知道有个方法叫Response.Flush(),一般我们调用它都是在程序末尾,但注意这个方法可以被调用多次。目的是可以将现有的缓存中的回复内容先发给客户端,让客户端“有活干”。

那在什么时候调用这个方法比较好呢?一般情况下我们可以在对于需要加载比较多外部脚本或者样式表时可以提前调用一次,客户端收到了关于脚本或其他外部资源的链接可以并行的先发请求去下载,服务器接下来把后续的处理结果发给客户端。

使用GET Ajax请求

浏览器在实现XMLHttpRequest POST的时候分成两步,先发header,然后发送数据。而GET却可以用一个TCP报文完成请求。另外GET从语义上来讲是去服务器取数据,而POST则是向服务器发送数据,所以我们使用Ajax请求数据的时候尽量通过GET来完成。

关于GET和POST的详细对比可以查看这里

避免空的图片src

空的图片src仍然会使浏览器发送请求到服务器,这样完全是浪费时间,而且浪费服务器的资源。尤其是你的网站每天被很多人访问的时候,这种空请求造成的伤害不容忽略。

浏览器如此实现也是根据RFC 3986 - Uniform Resource Identifiers标准,空的src被定义为当前页面。

所以注意我们的网页中是否存在这样的代码

straight HTML 
<img src="">

JavaScript 
var img = new Image(); 
img.src = "";

Cookie

Cookie被用来做认证或个性化设置,其信息被包含在http报文头中,对于cookie我们要注意以下几点,来提高请求的响应速度,

  • 去除没有必要的cookie,如果网页不需要cookie就完全禁掉
  • 将cookie的大小减到最小
  • 注意cookie设置的domain级别,没有必要情况下不要影响到sub-domain
  • 设置合适的过期时间,比较长的过期时间可以提高响应速度。

关于asp.net中的cookie可以参考ASP.NET Cookies OverviewConfigure Use Cookies Mode for Session State (IIS 7)

大多数网站的静态资源都没必要cookie,我们可以采用不同的domain来单独存放这些静态文件,这样做不仅可以减少cookie大小从而提高响应速度,还有一个好处是有些proxy拒绝缓存带有cookie的内容,如果能将这些静态资源cookie去除,那就可以得到这些proxy的缓存支持。

常见的划分domain的方式是将静态文件放在static.example.com,动态内容放在www.example.com

也有一些网站需要在二级域名上应用cookie,所有的子域都会继承,这种情况下一般会再购买一个专门的域名来存放cookie-free的静态资源。例如Yahoo!的yimg.com,YouTube的ytimg.com等。

CSS

将样式表置顶

经样式表(css)放在网页的HEAD中会让网页显得加载速度更快,因为这样做可以使浏览器逐步加载已将下载的网页内容。这对内容比较多的网页尤其重要,用户不用一直等待在一个白屏上,而是可以先看已经下载的内容。

如果将样式表放在底部,浏览器会拒绝渲染已经下载的网页,因为大多数浏览器在实现时都努力避免重绘,样式表中的内容是绘制网页的关键信息,没有下载下来之前只好对不起观众了。

避免CSS表达式

CSS表达式可以动态的设置CSS属性,在IE5-IE8中支持,其他浏览器中表达式会被忽略。例如下面表达式在不同时间设置不同的背景颜色。

background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );

CSS表达式的问题在于它被重新计算的次数远比我们想象的要多,不仅在网页绘制或大小改变时计算,即使我们滚动屏幕或者移动鼠标的时候也在计算,因此我们还是尽量避免使用它来防止使用不当而造成的性能损耗。

如果想达到类似的效果我们可以通过简单的脚本做到。

前端网页与js性能优化
<html>
<head>
</head>
<body>
<script type="text/javascript">
var currentTime = new Date().getHours();
if (currentTime%2) {
if (document.body) {
document.body.style.background
= "#B8D4FF";
}
}
else {
if (document.body) {
document.body.style.background
= "#F08A00";
}
}
</script>
</body>
</html>
前端网页与js性能优化

避免使用@import的原因很简单,因为它相当于将css放在网页内容底部。

避免使用Filters

AlphaImageLoad也是IE5.5 - IE8中支持,这种滤镜的使用会导致图片在下载的时候阻塞网页绘制,另外使用这种滤镜会导致内存使用量的问题。IE9中已经不再支持。

Javascript

将脚本置底

HTTP/1.1 specification建议浏览器对同一个hostname不要超过两个并行下载连接, 所以当你从多个domain下载图片的时候可以提高并行下载连接数量。但是当脚本在下载的时候,即使是来自不同的hostname浏览器也不会下载其他资源,因为浏览器要在脚本下载之后依次解析和执行。

因此对于脚本提速,我们可以考虑以下方式,

  • 把脚本置底,这样可以让网页渲染所需要的内容尽快加载显示给用户。
  • 现在主流浏览器都支持defer关键字,可以指定脚本在文档加载后执行。
  • HTML5中新加了async关键字,可以让脚本异步执行。

使用外部Javascirpt和CSS文件

使用外部Javascript和CSS文件可以使这些文件被浏览器缓存,从而在不同的请求内容之间重用。

同时将Javascript和CSS从inline变为external也减小了网页内容的大小。

使用外部Javascript和CSS文件的决定因素在于这些外部文件的重用率,如果用户在浏览我们的页面时会访问多次相同页面或者可以重用脚本的不同页面,那么外部文件形式可以为你带来很大的好处。但对于用户通常只会访问一次的页面,例如microsoft.com首页,那inline的javascript和css相对来说可以提供更高的效率。

精简Javascript和CSS

精简就是将Javascript或CSS中的空格和注释全去掉,

前端网页与js性能优化
body {
line-height
: 1;
}
ol, ul
{
list-style
: none;
}
blockquote, q
{
quotes
: none;
}
前端网页与js性能优化

精简后版本

body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}

统计表明精简后的文件大小平均减少了21%,即使在应用Gzip的文件也会减少5%。

例如我的网站上有5个CSS,4个Javascirpt,下面是分别经过bundling和minify之后的结果。

没有任何处理之前 捆绑Javascript和CSS之后 精简Javascript和CSS之后
前端网页与js性能优化 前端网页与js性能优化 前端网页与js性能优化

用来帮助我们做精简的工具很多,主要可以参考如下,

JS compressors:

CSS compressors:

与VS集成比较好的工具如下.

去除重复脚本

重复的脚本不仅浪费浏览器的下载时间,而且浪费解析和执行时间。一般用来避免引入重复脚本的做法是使用统一的脚本管理模块,这样不仅可以避免重复脚本引入,还可以兼顾脚本依赖管理和版本管理。

减少DOM访问

通过Javascript访问DOM元素没有我们想象中快,元素多的网页尤其慢,对于Javascript对DOM的访问我们要注意

  • 缓存已经访问过的元素
  • Offline更新节点然后再加回DOM Tree
  • 避免通过Javascript修复layout

使用智能事件处理

这里说智能的事件处理需要开发者对事件处理有更深入的了解,通过不同的方式尽量少去触发事件,如果必要就尽早的去处理事件。

比如一个div中10个按钮都需要事件句柄,那么我们可以将事件放在div上,在事件冒泡过程中捕获该事件然后判断事件来源。

图片

优化图像

当美工完成了网站的图片设计后,我们可以在上传图片之前对其做以下优化

  • 检查GIF图片中图像颜色的数量是否和调色板规格一致。如果你发现图片中只用到了4种颜色,而在调色板的中显示的256色的颜色槽,那么这张图片就还有压缩的空间。可以使用imagemagick检查: 
    identify -verbose image.gif
  • 尝试把GIF格式转换成PNG格式,看看是否节省空间。大多数情况下是可以压缩的。下面这条简单的命令可以安全地把GIF格式转换为PNG格式: 
    convert image.gif image.png
  • 在所有的PNG图片上运行pngcrush(或者其它PNG优化工具)。例如: 
    pngcrush image.png -rem alla -reduce -brute result.png
  • 在所有的JPEG图片上运行jpegtran。这个工具可以对图片中的出现的锯齿等做无损操作,同时它还可以用于优化和清除图片中的注释以及其它无用信息 
    jpegtran -copy none -optimize -perfect src.jpg dest.jpg

优化CSS Sprite

  • Spirite中水平排列图片,垂直排列会增加文件大小;
  • Spirite中把颜色较近的组合在一起可以降低颜色数,理想状况是低于256色以便适用PNG8格式;
  • 不要在Spirite的图像中间留有较大空隙。这虽然不大会增加文件大小,但对于用户代理来说它需要更少的内存来把图片解压为像素地图。100×100的图片为1万像素,1000×1000就是100万像素。

不要在HTML中缩放图片

不要通过图片缩放来适应页面,如果你需要小图片,就直接使用小图片吧。

使用小且可缓存的favicon.ico

网站图标文件favicon.ico,不管你服务器有还是没有,浏览器都会去尝试请求这个图标。所以我们要确保这个图标

  • 存在
  • 文件尽量小,最好小于1k
  • 设置一个长的过期时间

移动客户端

保持单个内容小于25KB

这限制是因为iphone,他只能缓存小于25K,注意这是解压后的大小。所以单纯gzip不一定够用,精简文件工具要用上了。

打包组建成符合文档

把页面内容打包成复合文本就如同带有多附件的Email,它能够使你在一个HTTP请求中取得多个组建。当你使用这条规则时,首先要确定用户代理是否支持(iPhone不支持)。


 JavaScript性能优化

如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度。 这种情况下决定程序速度的另一个重要因素就是代码本身。

在这里我们会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍。

目录

变量查找优化

变量声明带上var

1. 如果声明变量忘记了var,那么js引擎将会遍历整个作用域查找这个变量,结果不管找到与否,都是悲剧。

  • 如果在上级作用域找到了这个变量,上级作用域变量的内容将被无声的改写,导致莫名奇妙的错误发生。
  • 如果在上级作用域没有找到该变量,这个变量将自动被声明为全局变量,然而却都找不到这个全局变量的定义。

2. 基于上面逻辑,性能方面不带var声明变量自然要比带var速度慢

具体可以参考http://jsperf.com/withvar-withoutvar。下面是个简单的结果截图,蓝色为带var的情况,越长说明 速度越快。

前端网页与js性能优化

慎用全局变量

1. 全局变量需要搜索更长的作用域链。

2. 全局变量的生命周期比局部变量长,不利于内存释放。

3. 过多的全局变量容易造成混淆,增大产生bug的可能性。

全局变量与局部变量的测试可以参考http://jsperf.com/local-global-var

 

以上两条还可以得出一条JavaScript常用的编程风格具有相同作用域变量通过一个var声明 。

这样方便查看该作用域所有的变量,JQuery源代码中就是用了这种风格。例如下面源代码

https://github.com/jquery/jquery/blob/master/src/core.js

1234 jQuery.extend
= jQuery.fn.extend =
function() {
var
options, name, src, copy, copyIsArray, clone,target = arguments[0] || {},i = 1,length =
 arguments.length,deep
=
false;

缓存重复使用的全局变量

1. 全局变量要比局部变量需要搜索的作用域长

2. 重复调用的方法也可以通过局部缓存来提速

3. 该项优化在IE上体现比较明显

缓存与不缓存变量的测试可以参考http://jsperf.com/localvarcache

JQuery源代码中也是用了类似的方法,https://github.com/jquery/jquery/blob/master/src/selector-native.js

12345678910 var
docElem = window.document.documentElement, selector_hasDuplicate,
matches
= docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector ||
 docElem.msMatchesSelector,selector_sortOrder
=
function( a, b ) {
//
Flag for duplicate removal
if
( a === b ) {
    selector_hasDuplicate = true;    return
0;
}

避免使用with

with语句将一个新的可变对象推入作用域链的头部,函数的所有局部变量现在处于第二个作用域链对象中,从而使局部变 量的访问代价提高。

1234567891011 var
person = {
    name: “Nicholas",    age: 30}function
displayInfo() {
    var
count = 5;
    with
(person) {
        alert(name + ' is ' + age);        alert('count is ' + count);    }}

以上代码的结果将name和age两个变量推入第一个作用域,如下图所示,

前端网页与js性能优化

使用with与不使用with的测试可以参考http://jsperf.com/with-with

核心语法优化

通过原型优化方法定义

1. 如果一个方法类型将被频繁构造,通过方法原型从外面定义附加方法,从而避免方法的重复定义。 
2. 可以通过外 部原型的构造方式初始化值类型的变量定义。(这里强调值类型的原因是,引用类型如果在原型中定义, 一个实例对引用类型的更改会影响到其他实例。)

这条规则中涉及到JavaScript中原型的概念,

  • 构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。我们可 以把那些不变的属性和方法,直接定义在prototype对象上。
  • 可以通过对象实例访问保存在原型中的值,不能通过对象实例重写原型中的值。
  • 在实例中添加一个与实例原型同名属性,那该属性就会屏蔽原型中的属性。
  • 通过delete操作符可以删除实例中的属性。

例如以下代码以及相应的内存中原型表示如下,

1234567891011 function
Person(){}
Person.prototype.name
=
"Nicholas";
Person.prototype.age
= 29;
Person.prototype.job
=
"Software Engineer";
Person.prototype.sayName
=
function(){
    alert(this.name);};var
person1 =
new
Person();
person1.sayName();
//”Nicholas”
var
person2 =
new
Person();
person2.sayName();
//”Nicholas”

前端网页与js性能优化

原型附加方法测试可以参考http://jsperf.com/func-constructor

原型附加值类型变量测试可以参考http://jsperf.com/prototype2

避开闭包陷阱

1. 闭包是个强大的工具,但同时也是性能问题的主要诱因之一。不合理的使用闭包会导致内存泄漏。

2. 闭包的性能不如使用内部方法,更不如重用外部方法。

由于IE浏览器的DOM是用COM来实现的, COM的内存管理是通过引用计数的方式,引用计数有个难题就是循环引用,一旦DOM 引用了闭包(例如event handler),闭包的上层元素又引用了这个DOM,就会造成循环引用从而导致内存泄漏。

前端网页与js性能优化

关于Js内存泄漏可以参考

http://www.crockford.com/javascript/memory/leak.html

http://msdn.microsoft.com/en-us/library/bb250448%28v=vs.85%29.aspx

闭包与非闭包的测试http://jsperf.com/closure2

避免使用属性访问方法

1. JavaScript不需要属性访问方法,因为所有的属性都是外部可见的。 
2. 添加属性访问方法只是增加了一层重定向 ,对于访问控制没有意义。

使用属性访问方法示例

1234567891011121314151617 function
Car() {    
  this.m_tireSize = 17;       this.m_maxSpeed = 250;  this.GetTireSize = Car_get_tireSize;       this.SetTireSize = Car_put_tireSize;} function
Car_get_tireSize() {    
  return
this
.m_tireSize;
} function
Car_put_tireSize(value) {    
  this.m_tireSize = value;}var
ooCar =
new
Car();
var
iTireSize = ooCar.GetTireSize();
ooCar.SetTireSize(iTireSize
+ 1);

直接访问属性示例

1234567 function
Car() {    
  this.m_tireSize = 17;       this.m_maxSpeed = 250;}var
perfCar =
new
Car();
var
iTireSize = perfCar.m_tireSize;
perfCar.m_tireSize
= iTireSize + 1;

使用属性访问与不使用属性访问的测试http://jsperf.com/property-accessor

避免在循环中使用try-catch

1. try-catch-finally语句在catch语句被执行的过程中会动态构造变量插入到当前域中,对性能有一定影响。 
2. 如 果需要异常处理机制,可以将其放在循环外层使用。

循环中使用try-catch

123 for
(
var
i = 0; i < 200; i++) {
 try
{}
catch
(e) {}
}

循环外使用try-catch

123 try
{
 for
(
var
i = 0; i < 200; i++) {}
}
catch
(e) {}

循环内与循环外使用try-catch的测试http://jsperf.com/try-catch

使用for代替for…in…遍历数组

for…in…内部实现是构造一个所有元素的列表,包括array继承的属性,然后再开始循环。相对for循环性能要慢。

*上对这个for和for in的问题有个经典的回答,直接原文引用,

Q: I've been told not to use "for...in" with arrays in JavaScript. Why not?

A: The reason is that one construct...

123456 <code>var
a = [];
a[5]
= 5;
// Perfectly legal JavaScript that resizes the array.
 for
(
var
i=0; i<a.length; i++) {
    // Iterates over numeric indexes from 0 to 5, as everyone expects.}</code>

can sometimes be totally different from the other...

12345 <code>var
a = [];
a[5]
= 5;
for
(
var
x
in
a) {
    // Shows only the explicitly set index of "5", and ignores 0-4}</code>

Also consider that JavaScript libraries might do things like this, which will affect any array you create:

1234567891011 <code>// Somewhere deep in  your
JavaScript library...
Array.prototype.foo
= 1;
 //
Now you have no idea what the below code will do.
var
a = [1,2,3,4,5];
for
(
var
x
in
a){
    // Now foo is a part of EVERY array and     // will show up here as a value of 'x'.}</code>

关于for和for…in…的测试可以看http://jsperf.com/forin

使用原始操作代替方法调用

方法调用一般封装了原始操作,在性能要求高的逻辑中,可以使用原始操作代替方法调用来提高性能。

原始操作

1 var
min = a < b ? a : b;

方法实例

1 var
min = Math.min(a, b);

关于方法调用和原始操作的测试参考http://jsperf.com/operator-function

传递方法取代方法字符串

一些方法例如setTimeout()/setInterval(),接受字符串或者方法实例作为参数。直接传递方法对象作为参数来避免对字 符串的二次解析。

传递方法

1 setTimeout(test,
1);

传递方法字符串

1 setTimeout('test()', 1);

对应的测试可以参考http://jsperf.com/string-function

脚本装载优化

使用工具精简脚本

精简代码就是将代码中的空格和注释去除,也有更进一步的会对变量名称混淆+精简。

根据统计精简后文件大小平均减少21%,即使Gzip之后文件也会减少5%。

常用的工具如下,

例如Closure Compiler效果如下,

前端网页与js性能优化

启用Gzip压缩

Gzip通常可以减少70%网页内容的大小,包括脚本、样式表、图片等文件。Gzip比deflate更高效,主流服务器都有相应的 压缩支持模块。

Gzip的工作流程为

  • 客户端在请求Accept-Encoding中声明可以支持gzip
  • 服务器将请求文档压缩,并在Content-Encoding中声明该回复为gzip格式
  • 客户端收到之后按照gzip解压缩

前端网页与js性能优化

设置Cache-Control和Expires头

通过Cache-Control和Expires头可以将脚本文件缓存在客户端或者代理服务器上,可以减少脚本下载的时间。

123456789 Expires格式:Expires
= "Expires" ":" HTTP-date
Expires:
Thu, 01 Dec 1994 16:00:00 GMT
Note:
if a response includes a Cache-Control field with the max-age directive that directive overrides the
Expires
field.
 Cache-Control格式:Cache-Control  
= "Cache-Control" ":" 1#cache-directive
Cache-Control:
public

具体的标准定义可以参考http1.1中的定义,简单来说Expires控制过期时间是多久,Cache-Control控制什么地方可以缓存 。

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

异步加载脚本

脚本加载与解析会阻塞HTML渲染,可以通过异步加载方式来避免渲染阻塞。

异步加载的方式很多,比较通用的方法是通过类似下面的代码实现,

12345678910111213141516 function
loadjs
 (script_filename){    var
script = document.createElement(
'script');
    script.setAttribute('type','text/javascript');    script.setAttribute('src', script_filename);    script.setAttribute('id','script-id');     scriptElement = document.getElementById('script-id');    if(scriptElement){        document.getElementsByTagName('head')[0].removeChild(scriptElement);    }    document.getElementsByTagName('head')[0].appendChild(script);}var
script =
'scripts/alert.js';
loadjs(script);

DOM操作优化

DOM操作性能问题主要有以下原因,

  • DOM元素过多导致元素定位缓慢
  • 大量的DOM接口调用
  • DOM操作触发频繁的reflow(layout)和repaint

关于reflow(layout)和repaint可以参考下图,可以看到layout发生在repaint之前,所以layout相对来说会造成更多性能 损耗。

  • reflow(layout)就是计算页面元素的几何信息
  • repaint就是绘制页面元素

前端网页与js性能优化

以下是一个wikipedia网站reflow的过程录像,

 

减少DOM元素数量

1. 在console中执行命令查看DOM元素数量

1     document.getElementsByTagName('*').length

2. Yahoo首页DOM元素数量在1200左右。正常页面大小一般不应该超过 1000。 
3. DOM元素过多会使DOM元素查询效率,样式表匹配效率降低,是页面性能最主要的瓶颈之一。

优化CSS样式转换

如果需要动态更改CSS样式,尽量采用触发reflow次数较少的方式。

例如以下代码逐条更改元素的几何属性,理论上会触发多次reflow

123 element.style.fontWeight
=
'bold';
element.style.marginLeft=
'30px';
element.style.marginRight
=
'30px';

可以通过直接设置元素的className直接设置,只会触发一次reflow

123 element.className
=
 'selectedAnchor';

具体的测试结果如下,

前端网页与js性能优化

测试用例可以参考http://jsperf.com/css-class

优化节点添加

多个节点插入操作,即使在外面设置节点的元素和风格再插入,由于多个节点还是会引发多次reflow。优化的方法是创建 DocumentFragment,在其中插入节点后再添加到页面。

例如JQuery中所有的添加节点的操作如append,都是最终调用documentFragment来实现的,

http://code.jquery.com/jquery-1.10.2.js

123456789101112131415 function createSafeFragment(
document ) {
    var
list = nodeNames.split(
"|"
),
        safeFrag = document.createDocumentFragment();     if
( safeFrag.createElement ) {
        while
( list.length ) {
            safeFrag.createElement(                list.pop()            );        }    }    return
safeFrag;
}

关于documentFragment对比直接添加节点的测试http://jsperf.com/fragment2

优化节点修改

对于节点的修改,可以考虑使用cloneNode在外部更新节点然后再通过replace与原始节点互换。

123456789 var
orig = document.getElementById(
'container');
var
clone = orig.cloneNode(
true);
var
list = [
'foo','bar','baz'];
var
contents;
for
(
var
i = 0; i < list.length; i++) {
  content = document.createTextNode(list[i]);  clone.appendChild(content);}orig.parentNode.replaceChild(clone,
orig);

对应的测试可以参考http://jsperf.com/clone-node2

减少使用元素位置操作

一般浏览器都会使用增量reflow的方式将需要reflow的操作积累到一定程度然后再一起触发,但是如果脚本中要获取以下 属性,那么积累的reflow将会马上执行,已得到准确的位置信息。

  • offsetLeft
  • offsetTop
  • offsetHeight
  • offsetWidth
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • getComputedStyle()

具体讨论可以参考这个链接http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css- performance-making-your-javascript-slow/#comment-13157

避免遍历大量元素

避免对全局DOM元素进行遍历,如果parent已知可以指定parent在特定范围查询。

例如以下示例,

1234 var
elements = document.getElementsByTagName(
'*');
for
(i = 0; i < elements.length; i++) {
  if
(elements[i].hasAttribute(
'selected')) {}
}

如果已知元素存在于一个较小的范围内,

123456 var
elements = document.getElementById
 ('canvas').getElementsByTagName('*');for
(i = 0; i < elements.length; i++) {
  if
(elements[i].hasAttribute(
'selected')) {}
}
1  

相关测试可以参考http://jsperf.com/ranged-loop

事件优化

使用事件代理

1. 当存在多个元素需要注册事件时,在每个元素上绑定事件本身就会对性能有一定损耗。
2. 由于DOM Level2事件模 型中所有事件默认会传播到上层文档对象,可以借助这个机制在上层元素注册一个统一事件对不同子元素进行相应处理。

捕获型事件先发生。两种事件流会触发DOM中的所有对象,从document对象开始,也在document对象结束。

http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html

前端网页与js性能优化

示例代码如下

12345678 <ul
id="parent-list">
    <li
id="post-1">Item 1
    <li
id="post-2">Item 2
    <li
id="post-3">Item 3
    <li
id="post-4">Item 4
    <li
id="post-5">Item 5
    <li
id="post-6">Item 6
</li></ul>
123456789 //
Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click",function(e) {    // e.target is the clicked element!    // If it was a list item    if(e.target && e.target.nodeName == "LI") {        // List item found!  Output the ID!        console.log("List item ",e.target.id.replace("post-")," was clicked!");    }});

对应的测试可以参考http://jsperf.com/event- delegate

动画优化

动画效果在缺少硬件加速支持的情况下反应缓慢,例如手机客户端

特效应该只在确实能改善用户体验时才使用,而不应用于炫耀或者弥补功能与可用性上的缺陷

至少要给用户一个选择可以禁用动画效果

设置动画元素为absolute或fixed

position: static 或position: relative元素应用动画效果会造成频繁的reflow

position: absolute或position: fixed 的元素应用动画效果只需要repaint

关于position的具体介绍可以参考

http://css- tricks.com/almanac/properties/p/position/

使用一个timer完成多个元素动画

setInterval和setTimeout是两个常用的实现动画的接口,用以间隔更新元素的风格与布局。

动画效果的帧率最优化的情况是使用一个timer完成多个对象的动画效果,其原因在于多个timer的调用本身就会损耗一定 性能。

123456 setInterval(function() {  animateFirst('');},
10);
setInterval(function() {  animateSecond('');},
10);

使用同一个timer,

1234 setInterval(function() {  animateFirst('');  animateSecond('');},
10);

 

以上是JavaScript性能提高的技巧总结,基本上都能够通过测试验证,但是限于篇幅没有把所有的测试结果都 贴出来。