javascript性能优化

来源:互联网 发布:大数据认证 有用吗 编辑:程序博客网 时间:2024/06/06 01:14


近来,自己做了一个网站,部署到线上的时候,发现页面首页加载特别慢,于是乎,自己就开始查找原因,经过一番跟踪,原来发现是javascript执行的时候所致,下面是我在优化javascript总结的一些心得:

javascript脚本执行过程中会中断页面加载,直到脚本执行完毕,此操作阻塞了页面加载,造成性能问题。

脚本位置和加载顺序
如果将脚本放在head内,那么再脚本执行完毕之前,显示给用户的始终是一片空白,用户只能傻傻的看着屏幕等待脚本执行完毕。而且,如果页面引入多个脚本,那么后面的脚本文件必须等待前面的脚本文件下载完毕并且执行完毕之后才能开始下载并运行。不过IE8,FF,SAFARI,CHROME已经允许脚本文件可以同时下载,不过尽管如此,javascript脚本仍然会阻塞其他脚本下载进程,页面仍旧要等待所有javascript脚本下载并执行完毕之后才可以开始加载渲染。因此,尽可能的将脚本文件放置在body标签的底部,以减少脚本阻塞对页面性能的影响,这也是Yahoo性能优化的第一条定律。
成组脚本加载
我们知道较少HTTP请求数可以有效提高页面加载速度,泽卡斯说:“下载一个100KB的JS文件要比下载4个25KB的JS文件速度快”。毕竟有请求和响应的时间。所以我们可以将多个JS文件打包压缩成一个来提升性能。YUI通过CDN网络将客户端请求的多个JS文件在服务器端合并并压缩成一个返回给客户端,从而提高加载效率。例如:

<pre name="code" class="html">
<span style="font-family: Arial, Helvetica, sans-serif;"></span><pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; line-height: 25px; font-family: 'Courier New' !important;"><script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js "></script>
通过这个请求就可以把min.js和event-min.js两个文件在服务器端合并压缩成一个返回。

非阻塞脚本和延迟脚本(Deferred scripts)
虽然将多个javascript脚本合并并且将脚本放在body底部降低了HTTP请求数并且部分解决了阻塞问题。但如果脚本文件很大, 而且在每个脚本中都有功能函数运行,那么在脚本文件加载时,会占用浏览器很长一段时间,这段时间用户也只能傻傻的看着屏幕玩弄着没有任何反应的浏览器。为了避开这种情况,就出现了模块化加载和按需加载。

HTML4为<script>标签扩展了defer属性,设置过该属性的script脚本可以放在页面的任何位置,并且不会阻塞页面其他资源的下载,也就是说可以实现页面内容的并行加载。但下载完成后代码不会执行,只有等到DOM完全加载完毕后onload事件发生之前被执行。在《高性能JS》中,作者提到只有IE4和FF3.5的更高版本支持,不过在这篇文章中说webkit内核在HTML5也已经支持deffer和async。考虑到浏览器兼容性和更加强大和灵活的脚本控制,我们就需要引入按需加载
我们可以通过动态创建script标记,更改其属性,并添加至head内,完成对script加载顺序、时间、依赖关系的控制。

<strong><span style="font-family:Microsoft YaHei;">1 <span style="white-space:pre"></span>var script= document.createElement("script");2 <span style="white-space:pre"></span>script.type = "text/javascript";3<span style="white-space:pre"></span> script.src="file1.js";4 <span style="white-space:pre"></span>document.getElementsByTagName("head")[0].appendChild(script);</span></strong>
这样做的好处在于:无论在什么地方加载file1.js,都不会阻塞和影响页面其他内容的加载,当然,会影响HTTP请求。一般情况下,通过动态节点下载脚本文件时,file1.js被加载完毕之后,往往会立即执行(除了FF和Opera,他们会等待此前的所有动态节点脚本执行完毕)。当脚本文件是“自执行(function(){})()”时,一切都很正常,但若只是一般函数命名定义或者只是提供了相关接口,就会有一些问题(至于什么问题,我没有验证,但估计是脚本加载程度的问题,书中只说在这种情况下需要跟踪脚本下载完成并准备妥善的情况.若你有相关资料或者见解,非常希望能给出,并给予指点。)。针对这一情况,《高性能JS》中给出了一些解决方法:FF,Chrome,Safari,Opera会在<script>节点脚本接收完成后发出一个load事件,IE会给出readystatechanage事件(script元素有一个readyState属性,它的值随文件的下载状态而改变,共有5中取值,不在一一列出,我们在这里使用loaded和complete来表示下载完成和所有数据已经准备好)。我们可以通过这两个事件判断脚本是否加载完毕,下面是文中提供的一个封装好的函数,兼容各主流浏览器:
<ul><li><span style="font-family: 'Microsoft YaHei';">function loadScript(url, callback){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">var script = document.createElement("script")</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">script.type = "text/javascript";</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">if (script.readyState){//IE</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">script.onreadystatechange = function(){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">if (script.readyState == "loaded" || script.readyState == "complete"){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">script.onreadystatechange = null;</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">callback();</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">}</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">};</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">} else {//Others</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">script.onload = function(){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">callback();</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">};</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">}</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">script.src = url;</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">document.getElementsByTagName("head")[0].appendChild(script);</span></li><li><span style="font-family: 'Microsoft YaHei';">}</span></li></ul>
两个参数@url:javascript文件URL、@callback:javascript接收完成时的回调函数,最后设置src属性,将<script>元素添加至页面。这里为什么不是先设置src,然后才判断script加载情况呢?如果先设置了,还判断干毛?
如果我们要按顺序和依赖关系加载多个javascript脚本文件,浏览器在此时并不保证文件执行顺序。所有浏览器中只有FF和Opera保证脚本按照下载顺序执行,其他浏览器将按照服务器返回的顺序加载运行。那么我们就需要运用上面的例子在回调函数中按顺序加载执行脚本。
<ol><li><span style="font-family: 'Microsoft YaHei';">loadScript("file1.js", function(){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">loadScript("file2.js", function(){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">loadScript("file3.js", function(){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">alert("All files are loaded!");</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">});</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">});</span></li><li><span style="font-family: 'Microsoft YaHei';">});</span></li></ol>
不过这样明显很麻烦,而且在速度上也是个问题。

LABjs:

Kyle Simpson写的开源库LABjs,精缩后4.5K,据说对并行下载和精确控制依赖关系更有针对性。

<ol><li><span style="font-family: 'Microsoft YaHei';"><script type="text/javascript" src="lab.js"></script></span></li><li><span style="font-family: 'Microsoft YaHei';"><script type="text/javascript"></span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">$LAB.script("the-rest.js,"<span style="color: rgb(51, 51, 51); font-family: 'Microsoft YaHei';font-size:12px; line-height: 25px; white-space: pre; background-color: rgb(240, 240, 240);">the-rest.js</span>"")</span><span style="font-family: 'Microsoft YaHei';">.wait(function(){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">Application.init();</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">});</span></li><li><span style="font-family: 'Microsoft YaHei';"></script></span></li></ol>
LAB支持链式操作,每个函数默认返回一个$LAB对象的引用,要加载多个脚本,可以这样:

如果想管理依赖关系,可以通过wait函数,这样:

<ol><li><span style="font-family: 'Microsoft YaHei';"><script type="text/javascript" src="lab.js"></script></span></li><li><span style="font-family: 'Microsoft YaHei';"><script type="text/javascript"></span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">$LAB.script("first-file.js")<span style="color: rgb(51, 51, 51); font-family: 'Microsoft YaHei';font-size:12px; line-height: 25px; white-space: pre; background-color: rgb(240, 240, 240);">.wait()</span></span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">    </span><span style="font-family: 'Microsoft YaHei';">.script("the-rest.js</span><span style="font-family: 'Microsoft YaHei';">")</span><span style="font-family: 'Microsoft YaHei';">.wait(function(){</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">Application.init();</span></li><li><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">});</span></li><li><span style="font-family: 'Microsoft YaHei';"></script></span></li></ol>
此时,虽然文件是并行下载,但first-file.js一定会在the-rest.js之前执行。
RequireJS:
jrburke 的 RequireJS。
例子:
<ol><li style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; white-space: pre;"><span style="font-family: 'Microsoft YaHei';"><script data-main="scripts/main" src="scripts/require.js"></script></span></li><li style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; white-space: pre;"><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">require(["helper/util"], function() {</span></li><li style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; white-space: pre;"><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">//This function is called when scripts/helper/util.js is loaded.</span></li><li style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; white-space: pre;"><span style="font-family: 'Microsoft YaHei';"></span><span style="font-family: 'Microsoft YaHei';">});</span></li><li><span style="font-family:Microsoft YaHei;color:#333333;"><span style="font-size: 14px; line-height: 25px;"></span></span></li><li><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';"></span><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';">//加载多个JS:</span></li><li><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';"></span><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';">require(["helper/util","helper/util1","helper/util2","helper/util3"], function() {</span></li><li><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';"></span><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';">//This function is called when scripts/helper/util.js is loaded.</span></li><li><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';"></span><span style="color: rgb(51, 51, 51); font-size: 14px; line-height: 25px; font-family: 'Microsoft YaHei';">});</span></li><li><span style="font-family: 'Microsoft YaHei'; color: rgb(51, 51, 51); font-size: 14px; line-height: 25px;"></script></span></li></ol>

javascriptMVC  StealJS -> steal

steal是一个帮助你加载js,css和其他资源到你应用程序的函数。

加载第一个脚本

  一旦steal添加到你的页面,他就开始加载脚本了。我想加载myapp.js文件,想让他加载jquery.js 和jquery.ui.tabs.js。但是默认steal想让你把脚本放在steal的根目录下,在这个例子中public目录就是根目录。要加载myapp.js文件,我们有两种方式:

  1.在完成steal引用之后,我们可以像这样去加载myapp.js

<ol><li><span style="font-family: 'Microsoft YaHei';"><script type='text/javascript'></span></li><li><span style="font-family: 'Microsoft YaHei';">  steal('myapp/myapp.js');</span></li><li><span style="font-family: 'Microsoft YaHei';"></script></span></li></ol>

我们想加载jquery.js 和jquery.ui.tabs.js到页面,然后使用tabs插件,首先我们需要加载jquery.js。默认steal是加载steal根目录下的文件,加载myapp/jquery.js文件,我们跟着myapp.js的scripts,使用下面的scripts标签:

加载多个js文件,我们可以这样:

steal('./jquery.js','./jquery.ui.tabs.js')

  这里有关于并发的两个问题:

1.steal会并发的加载两个js文件,但是没有按照顺序去执行文件;

2.jquery.ui.tabs.js依赖于jquery.js的加载。这样带来的问题就是jquery.ui.tabs.js在jquery.js之前被加载执行,我们可以使用steal.static.then去解决这个问题,他会在之前的js完成加载和执行之后才会去加载后面的js文件。上面的代码可以修改为:

steal('./jquery.js').then('./jquery.ui.tabs.js')

  加载完js之后,添加回调函数执行我们需要的代码,例如完成jquery.js和jquery.ui.tabs.js加载之后,使用tabs插件:

steal('./jquery.js').then('./jquery.ui.tabs.js', function($){
  $('#tabs').tabs();
})



0 0
原创粉丝点击