JavaScript大杂烩17 - 性能优化

  在上一节推荐实践中其实很多方面是与效率有关的,但那些都是语言层次的优化,这一节偏重学习大的方面的优化,比如JavaScript脚本的组织,加载,压缩等等。 当然在此之前,分析一下浏览器的特征还是很有意义的。

浏览器的特征
1. 浏览器的渲染过程
  在详细讨论脚本文件的优化前,我们先来看一下浏览器是如何渲染一个HTML页面的。
  当浏览器渲染一个HTML页面的时候,它总是从页面的开始位置按顺序向页面末尾依次渲染,当页面遇到引用外部文件的时候(JavaScript脚本,CSS文件等),页面渲染就会停下来等待该外部文件下载完并执行完成,然后继续向下渲染。
  浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或相关代码,这些操作对后面页面内容造成影响。于是当浏览器遇到<script>标签时,由于当前HTML页面无从获知该JavaScript代码是否会向页面添加内容,或引入其他元素,或甚至移除某些标签元素,因此,这时浏览器会停止处理页面,先执行JavaScript代码,然后再继续解析和渲染页面。
  同样的情况也发生在使用src属性加载JavaScript的过程中,浏览器必须先花时间下载外链文件中的代码,然后解析并执行它。JavaScript执行过程耗时越久,浏览器等待响应用户输入的时间就越长。在这个过程中,页面渲染和用户交互完全被阻塞了。

2. 多线程的浏览器
  在上面的过程中,至少使用到了浏览器的HTTP申请线程,界面渲染引擎线程(大多数浏览器JavaScript解释引擎也包含在这个线程中,这个也解释了为什么执行JavaScript的时候会阻塞界面渲染),浏览器事件管理线程,这些线程都是由浏览器创建和管理的,这些线程执行特定的功能(从名称上就能看出来)并互相协作,完成页面的申请,资源的下载,页面的渲染,事件的处理等行为。

3. 单线程的JavaScript解释引擎
  咱们再把目光聚焦在JavaScript解释引擎上,毫无疑问,JavaScript脚本就是由它解释执行的。
  当我们执行一个行为的时候,这个行为的功能通常都是由JavaScript引擎和浏览器其它线程协作完成的。
  JavaScript解释引擎是单线程的,每个window(或者说一个页面吧)一个JS线程,既然是单线程的,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。
  与此同时,我们知道浏览器是事件驱动的 (Event driven) ,浏览器中很多行为是异步(Asynchronized)的,当异步事件发生时,如鼠标点击事件,setTimeout延时触发,Ajax返回,触发回调等,它会创建事件并通知JavaScript引擎把事件回调函数放到执行队列中,等待当前代码执行完成后去执行。JavasSript的任务队列就是由普通函数和回调函数构成的。
  这就是JavaScript单线程与回调函数的执行特点,这一点也被后台的NodeJS解释器(本来就是浏览器的解释器,所以一样是肯定的)继承了,它们这个方面的特性是如出一辙。
  到这里,我们再分析一下同学们常用的setTimeout(func, 0)的作用,从上面的原理来看,setTimeout(func, 0)神奇之处就是告诉JavaScript引擎,在0ms以后把func放到任务队列中,等待当前的代码执行完毕再执行,这里的重点是改变了代码的执行流程,这样就可能完成一些特殊的功能。

  好了,分析完浏览器,下面开始看看这些方面对脚本影响。

脚本组织 - 静态结构与动态组织 
1. 脚本的位置 
  我们知道JavaScript代码是放在script元素中的,可以直接嵌套在该元素中,也可以通过src去引用外部的js文件。

  我们看看script元素可以放置的位置。 
1). 放置在head区域 
  通过上面浏览器特征的分析,这个区域不太好,因为会阻塞下面body的渲染。 
2). 放置在body区域 
  通过上面浏览器特征的分析,把脚本放到body的最后是比较好的,因为不会阻塞上面的元素的渲染。

2. 脚本的数量 
  由于每个<script>标签下载时都会阻塞页面渲染,所以减少页面包含的<script>标签数量有助于改善这一情况。这不仅针对外链脚本,内嵌脚本的数量同样也要限制。浏览器在解析HTML页面的过程中每遇到一个<script>标签,都会因执行脚本而导致一定的延时,因此最小化延迟时间将会明显改善页面的总体性能。 
  这个问题在处理外链 JavaScript 文件时略有不同。考虑到HTTP请求会带来额外的性能开销,因此下载单个100Kb的文件将比下载 5个20Kb的文件更快。也就是说,减少页面中外链脚本的数量将会改善性能。 
  通常一个大型网站或应用需要依赖数个JavaScript文件。您可以把多个文件合并成一个,这样只需要引用一个<script>标签,就可以减少性能消耗。 
  但是还需要注意的是,如果这个合并的文件过大的话,可能会导致一次的下载时间太长而带来别的问题,这时我们就要考虑采用别的方法来提高效率了,比如动态加载。

3. 动态加载 
  动态加载,从我个人角度来说就是按需加载,页面打开的时候先加载必须的一些脚本,然后当需要的时候,再加载后续的脚本,当然了,这些脚本也可以以异步的方式在背后先加载好,当需要的时候直接使用即可。看一个动态加载的例子:

(function() {
    var s = document.createElement(‘script‘);
    s.type = ‘text/javascript‘;
    s.async = true;
    s.src = ‘http://yourdomain.com/script.js‘;
    var x = document.getElementsByTagName(‘script‘)[0];
    x.parentNode.insertBefore(s, x);
})(); 

脚本的异步与延迟执行 
1. 异步加载 
  上面同步加载脚本的过程是相当耗时的,如果我们能同时加载多个脚本文件,而且不阻塞页面的渲染线程,是不是能提高效率呢?这就是异步加载的思路,异步加载又叫非阻塞加载,浏览器在下载执行JavaScript代码的同时,还会继续进行后续页面的处理。当同步严重阻碍产品的可用性的时候,异步是势在必行的,这是软件技术发展的一个基本模式。实现脚本的异步加载有很多方式,下面逐一来看一下。 
1). 使用script自身特性 
  HTML5为<script>标签定义了一个新的扩展属性:async。它能够异步地加载和执行脚本,不因为加载脚本而阻塞页面的加载。但是有一点需要注意,在有async的情况下,JavaScript脚本一旦下载好了就会执行,所以很有可能不是按照原本的顺序来执行的。如果 JavaScript脚本前后有依赖性,使用async就很有可能出现错误。看个例子:

<script src="demo_async.js" async="async"></script>

  与async用于相似功能的另一个属性是defer,它也是异步的加载脚本,看个例子:

<script src="file.js" defer="defer"></script> 

  不过只有IE和FirFox支持defer属性,其他的浏览器不支持,所以这里就不多讲了,因为我们开发的网站可不仅仅只能在IE和FireFox上能用。

2). 动态异步加载 
  把上一个例子中的HTML代码换成JavaScript代码就变成动态异步加载了,就是我们上面的动态加载的代码,这里再拷贝一遍:

(function() {
    var s = document.createElement(‘script‘);
    s.type = ‘text/javascript‘;
    s.async = true;
    s.src = ‘http://yourdomain.com/script.js‘;
    var x = document.getElementsByTagName(‘script‘)[0];
    x.parentNode.insertBefore(s, x);
})(); 

  但是,这种实现的加载方式在加载执行完之前会阻止onload事件的触发,而现在很多页面的代码都在onload时还要执行额外的渲染工作等,所以还是会阻塞部分页面的初始化处理。于是把加载的时机放到onload触发后就比较好了,大概就是这样:

(function() {
    function async_load() {
        var s = document.createElement(‘script‘);
        s.type = ‘text/javascript‘;
        s.async = true;
        s.src = ‘http://yourdomain.com/script.js‘;
        var x = document.getElementsByTagName(‘script‘)[0];
        x.parentNode.insertBefore(s, x);
    }
    if (window.attachEvent) {
        window.attachEvent(‘onload‘, async_load); 

    }
    else {
        window.addEventListener(‘load‘, async_load, false); 

    }
})(); 

  这里的关键就是挂接事件的那几句代码,这里的实现代码中,它不是立即开始异步加载js,而是在onload事件触发时才开始异步加载。这样就解决了阻塞onload事件触发的问题。

  这里需要理解的是onload事件是在页面的所有资源都加载完毕(包括图片)后触发的,这时浏览器的载入进度已停止了。与这个事件相关的另外一个事件是DOMContentLoaded事件,这个事件是在页面(document)已经解析完成,页面中的dom元素已经可用是触发的,但是这个时候页面中引用的图片、subframe等可能还没有加载完。 
3). 使用Ajax实现加载 
  说起异步加载,我们不得不提到Ajax,使用Ajax可以轻松实现异步加载,而使用JQuery的实现无疑更是简单明了。看下面的例子:

// 使用Ajax方式实现异步加载
var xhr = new XMLHttpRequest();
xhr.open("get", "script1.js", true);
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
            var script = document.createElement ("script");
            script.type = "text/javascript";
            script.text = xhr.responseText;
            document.body.appendChild(script);
        }
    }
};
xhr.send(null);
// 使用JQuery简化加载过程
// 获取并执行一段脚本的快捷方法:jQuery.getScript( url, [callback] )
// 方法相当于: jQuery.get(url, null, [callback], ‘script‘)
$.getScript(‘../scripts/Sample.js‘, function(data, textStatus) {
    alert(data);
    alert(textStatus);
}); 

  这个在总结Ajax与JQuery的着重说过了,不再啰嗦。

4). 模块化管理 - 解决顺序问题 
  异步加载,需要将所有js内容按模块化的方式来切分组织,其中每个js文件就可能存在依赖关系,而异步加载不保证执行顺序,这就是个问题了。解决这个问题常见的就是使用模块化管理脚本,加上异步的特性,就是异步模块化管理。 
  异步模块化管理的概念,也就是AMD (Asynchronous Module Definition)的设计理念已经在目前较为流行的前端框架中大行其道了,像JQuery, Dojo, MooTools, EmbedJS 这种大型的框架纷纷在其最新版本中加入了对AMD的支持。 
  同时,一些只注重模块管理的精悍型类库也不断涌现,比如国外的RequireJS框架,它就只使用AMD的特性。使用RequireJS可以帮助用户异步按需的加载JavaScript代码,并解决JavaScript模块间的依赖关系,提升了前端代码的整体质量和性能。 
  下面这几篇文章讲述了RequireJS框架的用法: 
http://www.ibm.com/developerworks/cn/web/1209_shiwei_requirejs/ 
http://makingmobile.org/docs/tools/requirejs-api-zh/ 
http://requirejs.org/ 
  而国内的同行们在这方面也在不断努力,终于SeaJS框架横空出世,下面几篇教程就是讲述SeaJS的用法: 
http://seajs.org/ 
http://www.zhangxinxu.com/sp/seajs/docs/zh-cn/index.html 
http://www.2cto.com/kf/201312/268256.html

2. 延迟执行 
  不管是同步加载,还是异步加载,脚本加载完了就会立即执行,如果我们想在需要的时候才执行的话,就需要采用一些特殊的手段,看下面的例子:

var se = new Image();
se.onload = registerScript();
se.src = ‘http://anydomain.com/A.js‘; 

  它利用了图片的src指向的资源是异步下载的特点实现了异步加载,同时利用onload来注册脚本,然后在需要的时候再使用即可。

  不过,我们通常也可以在脚本代码中使用自执行函数把实际的功能封装起来变成一个对象返回回来,这样在需要的时候我们就可以调用这个对象去完成一定的功能,就像这样:

var app.util = (function() {
    var f1 = function() {};
    var f2 = function() {}; 

    return {
        F1: f1,
        F2: f2
    };
})(); 

3. 压缩

  这个一般指两个方面: 
第一方面:文件自身的压缩 
  因为脚本等文件(Js, css, html, xml, text, inline script, 一个都不能少)是要在网上传输的,那么脚本中的空格,备注,长变量其实都是对传输效率有害的。所以我们需要对文件经行压缩。这个是通过相关的工具实现的,一般只要选择成熟的工具,细节就不用我们管了,比如下列的一些工具: 
http://webmedia.blog.163.com/blog/static/416695020123202150472/ 
http://yui.github.io/yuicompressor/ 
https://developers.google.com/speed/articles/compressing-javascript?csw=1 
https://developers.google.com/closure/compiler/?csw=1 
http://www.cnblogs.com/JeffreyZhao/archive/2009/12/09/ikvm-google-closure-compiler.html

第二方面:GZIP压缩 
  对页面GZIP压缩几乎是每篇讲解高性能WEB程序的几大做法之一,因为使用GZIP压缩可以降低服务器发送的字节数,能让客户感觉到网页的速度更快,也减少了对带宽的使用情况。当然,这里也存在客户端的浏览器是否支持它(大多数原因是由于accept-encoding head头会丢失,由于防火墙,安全过滤软件等原因)。因此,我们通常要做的是,如果客户端支持GZIP,我们就发送GZIP压缩过的内容,如果不支持,我们直接发送静态文件的内容。服务器端可以配置两套代码,通过accept-encoding的信息来判断。GZIP应该是在部署的时候直接部署压缩过的文件。而不是执行压缩代码来压缩文件。那样会消耗服务器的性能。 
  不过目前似乎主流的浏览器默认都是支持GZIP的,所以我们通常也只需要在服务端开启GZIP压缩就可以了,下面就是一篇教程: 
http://www.chinaz.com/web/2012/1017/278682.shtml

JavaScript大杂烩17 - 性能优化

时间: 2024-10-10 20:48:30

JavaScript大杂烩17 - 性能优化的相关文章

javascript中数据访问性能优化简析

我们一般写代码都会考虑代码的可读性.可扩展性及重要的是浏览器的解析.减少代码数量提高代码性能加载速度,是我们每个coder所追求的.在javascript中,我们有时必须考虑的是如何确定数据的存储位置,以获取最佳的读写效率,数据存储的位置,关系到代码执行过程中数据被检索的速度,数据的存储位置在很大程度会影响其读取速度. javascript中有4种基本的数据存取位置:直接量.变量.数组元素.对象才成员 每种数据存储的位置都有不同的读写消耗.一般的情况下,从一个直接量和局部变量中存取数据的性能差异

前端性能优化之重排和重绘

一.重排 & 重绘 有经验的大佬对这个概念一定不会陌生,"浏览器输入URL发生了什么".估计大家已经烂熟于心了,从计算机网络到JS引擎,一路飞奔到浏览器渲染引擎. 经验越多就能理解的越深. 感兴趣的同学可以看一下这篇文章,深度和广度俱佳: <从输入 URL 到页面加载的过程?如何由一道题完善自己的前端知识体系!> 切回正题,我们继续探讨何为重排.浏览器下载完页面所有的资源后,就要开始构建DOM树,与此同时还会构建渲染树(Render Tree).(其实在构建渲染树之

[转载]网站前端性能优化之javascript和css——网站性能优化

之前看过Yahoo团队写的一篇关于网站性能优化的文章,文章是2010年左右写的,虽然有点老,但是很多方面还是很有借鉴意义的.关于css的性能优化,他提到了如下几点: CSS性能优化 1.把样式表置于顶部 现把样式表放到文档的< head />内部似乎会加快页面的下载速度.这是因为把样式表放到< head />内会使页面有步骤的加载显示. 注重性能的前端服务器往往希望页面有秩序地加载.同时,我们也希望浏览器把已经接收到内容尽可能显示出来.这对于拥有较多内容的页面和网速较慢的用户来说特

javascript动手写日历组件(3)——内存和性能优化(by vczero)

一.列表 javascript动手写日历组件的文章列表,主要是通过原生的JavaScript写的一个简约的日历组件: (1)javascript动手写日历组件(1)——构建日历逻辑:http://www.cnblogs.com/vczero/p/js_ui_1.html (2)javascript动手写日历组件(2)——优化UI和添加交互:http://www.cnblogs.com/vczero/p/js_ui_2.html (3)javascript动手写日历组件(2)——内存和性能优化:h

[转]JavaScript性能优化

如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍. 变量声明带上var 如果声明变量忘记了var,那么js引擎将会遍历整个作用域查找这个变量,结果不管找到与否,都是悲剧. 如果在上级作用域找到了这个变量,

Web性能优化系列:10个JavaScript性能提升的技巧

由 伯乐在线 - Delostik 翻译,黄利民 校稿.未经许可,禁止转载!英文出处:jonraasch.com.欢迎加入翻译小组. Nicholas Zakas是一位 JS 大师,Yahoo! 首页的前端主程.他是<高性能 Javascript>的作者,这本书值得每个程序员去阅读. 当谈到 JS 性能的时候,Zakas差不多就是你要找的,2010年六月他在Google Tech Talk发表了名为<Speed Up Your Javascript>的演讲. 但 Javascrip

【转】网站前端性能优化之javascript和css

之前看过Yahoo团队写的一篇关于网站性能优化的文章,文章是2010年左右写的,虽然有点老,但是很多方面还是很有借鉴意义的.关于css的性能优化,他提到了如下几点: CSS性能优化 1.把样式表置于顶部 现把样式表放到文档的< head />内部似乎会加快页面的下载速度.这是因为把样式表放到< head />内会使页面有步骤的加载显示. 注重性能的前端服务器往往希望页面有秩序地加载.同时,我们也希望浏览器把已经接收到内容尽可能显示出来.这对于拥有较多内容的页面和网速较慢的用 户来说

JavaScript性能优化

1.使用局部变量 在函数中,总是使用var来定义变量.无论何时使用var都会在当前的范围类创建一个局部变量.如果不使用var来定义变量,那么变量会被创建在window范围内,那么每次使用这个变量的时候,解释程序都会搜索整个范围树.同时全局变量要在页面从浏览器中卸载后才销毁,而局部变量在函数执行完毕即可销毁,过多的全局变量增加了不必要的内存消耗.只要有可能就应该用局部变量或者数字索引的数组来替代命名特性.如果命名特性要多次使用,就先将它的值存储在局部变量中,以避免多次使用线性算法请求命名特性的值.

JavaScript性能优化小知识总结(转)

JavaScript的性能问题不容小觑,这就需要我们开发人员在编写JavaScript程序时多注意一些细节,本文非常详细的介绍了一下JavaScript性能优化方面的知识点,绝对是干货. 前言 一直在学习javascript,也有看过<犀利开发Jquery内核详解与实践>,对这本书的评价只有两个字犀利,可能是对javascript理解的还不够透彻异或是自己太笨,更多的是自己不擅于思考懒得思考以至于里面说的一些精髓都没有太深入的理解. 鉴于想让自己有一个提升,进不了一个更加广阔的天地,总得找一个