《高性能JavaScript》读书笔记

1.加载和运行

1.把<script>标签放在尽可能接近<body>标签底部的位置

因为浏览器在遇到<body>标签之前,不会渲染页面的任何部分。

2.减少<script>标签总数

Yahoo! 为他的“Yahoo! 用户接口(Yahoo! User Interface,YUI)”库创建一个“联合句柄”,这是通过他们的“内容投递网络(Content Delivery Network,CDN)”实现的。任何一个网站可以使用一个“联合句柄”URL指出包含 YUI 文件包中的哪些文件。例如,下面的 URL 包含两个文件:

http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js

此 URL 调用 2.7.0 版本的 yahoo-min.js 和 event-min.js 文件。这些文件在服务器上是两个分离的文件,但是当服务器收到此 URL 请求时,两个文件将被合并在一起返回给客户端。通过这种方法,就不再需要两个<script>标签(每个标签加载一个文件),一个<script>标签就可以加载他们 。

3.非阻塞脚本

1.defer

<script type="text/javascript" src="file1.js" defer></script>

一个带有 defer 属性的<script>标签可以放置在文档的任何位置。对应的 JavaScript 文件将在<script>被解析时启动下载,但代码不会被执行,直到 DOM 加载完成(在 onload 事件句柄被调用之前)。当一个 defer的 JavaScript 文件被下载时,它不会阻塞浏览器的其他处理过程,所以这些文件可以与页面的其他资源一起并行下载。 


但是,defer 属性只被 Internet Explorer 4 和 Firefox 3.5 更高版本的浏览器所支持,它不是一个理想的跨浏览器解决方案。在其他浏览器上,defer 属性被忽略,<script>标签按照默认方式被处理(造成阻塞)。 

2.动态脚本元素 

var script = document.createElement ("script");

script.type = "text/javascript";
script.src = "file1.js";document.getElementsByTagName_r("head")[0].appendChild(script); 

新的<script>元素加载 file1.js 源文件。此文件当元素添加到页面之后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。你甚至可以将这些代码放在<head>部分而不会对其余部分的页面代码造成影响(除了用于下载文件的 HTTP 连接)。 


3.封装js动态加载的函数

function loadScript(url, callback){
var script = document.createElement ("script")script.type = "text/javascript";
if (script.readyState){
//IE

script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){

script.onreadystatechange = null;

callback();}

};
} else {
//Others

script.onload = function(){callback();

};}

script.src = url;

document.getElementsByTagName_r("head")[0].appendChild(script);} 


4.推荐的非阻塞模式

<script type="text/javascript" src="loader.js"></script><script type="text/javascript">

loadScript("the-rest.js", function(){Application.init();

});</script> 

或者:

<script type="text/javascript">function loadScript(url, callback){

var script = document.createElement ("script")script.type = "text/javascript";

if (script.readyState){//IEscript.onreadystatechange = function(){

if (script.readyState == "loaded" ||script.readyState == "complete"){

script.onreadystatechange = null;callback();

}};

} else {//Othersscript.onload = function(){

callback();};

}
script.src = url;document.getElementsByTagName_r("head")[0].appendChild(script);

}
loadScript("the-rest.js", function(){

Application.init();});

</script> 

2.数据访问

1.作用域链


函数中局部变量的访问速度总是最快的,而全局变量通常是最慢的 

全局变量总是处于运行期上下文作用域链的最后一个位置,所以总是最远才能触及的。 


一般来说,一个运行期上下文的作用域链不会被改变。但是,有两种表达式可以在运行时临时改变运行期上下文作用域链。第一个是 with 表达式。 

function initUI(){
with (document){
//avoid!

var bd = body,
links = getElementsByTagName_r("a"),i = 0,
len = links.length;
while(i < len){

update(links[i++]);}

getElementById("go-btn").onclick = function(){start();

};

bd.className = "active";}

当代码流执行到一个 with 表达式时,运行期上下文的作用域链被临时改变了。一个新的可变对象将被创建,它包含指定对象的所有属性。此对象被插入到作用域链的前端,意味着现在函数的所有局部变量都被推入第二个作用域链对象中,所以访问代价更高了 



通过将 document 对象传递给 with 表达式,一个新的可变对象容纳了 document 对象的所有属性,被插入到作用域链的前端。这使得访问 document 的属性非常快,但是访问局部变量的速度却变慢了,例如 bd 变量。正因为这个原因,最好不要使用 with 表达式。正如前面提到的,只要简单地将 document 存储在一个局部变量中,就可以获得性能上的提升( var doc = document)。 

在 JavaScript 中不只是 with 表达式人为地改变运行期上下文的作用域链,try-catch 表达式的 catch 子句具有相同效果。当 try 块发生错误时,程序流程自动转入 catch 块,并将异常对象推入作用域链前端的一个可变对象中。在 catch 块中,函数的所有局部变量现在被放在第二个作用域链对象中。例如:

try {methodThatMightCauseAnError();

} catch (ex){
alert(ex.message);
//scope chain is augmented here

只要 catch 子句执行完毕,作用域链就会返回到原来的状态。 

一个很好的模式是将错误交给一个专用函数来处理。例子如下:

try {methodThatMightCauseAnError();

} catch (ex){
handleError(ex);
//delegate to handler method

handleError()函数是 catch 子句中运行的唯一代码。此函数以适当方法自由地处理错误,并接收由错误产生的异常对象。由于只有一条语句,没有局部变量访问,作用域链临时改变就不会影响代码的性能。 


原型链


如果在同一个函数中你要多次读取同一个对象属性,最好将它存入一个局部变量。以局部变量替代属性,避免多余的属性查找带来性能开销。在处理嵌套对象成员时这点特别重要,它们会对运行速度产生难以置信的影响。 


总结:

局部变量比域外变量快,因为它位于作用域链的第一个对象中。变量在作用域链中的位置越深,访问所需
的时间就越长。全局变量总是最慢的,因为它们总是位于作用域链的最后一环。

避免使用 with 表达式,因为它改变了运行期上下文的作用域链。而且应当小心对待 try-catch 表达式的 catch子句,因为它具有同样效果。 

嵌套对象成员会造成重大性能影响,尽量少用。
一个属性或方法在原形链中的位置越深,访问它的速度就越慢。

一般来说,你可以通过这种方法提高 JavaScript 代码的性能:将经常使用的对象成员,数组项,和域外变量存入局部变量中。然后,访问局部变量的速度会快于那些原始变量。 


3.DOM编程

当浏览器下载完所有页面 HTML 标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据结构: 一棵DOM树表示页面结构,一棵渲染树表示 DOM 节点如何显示。

渲染树中为每个需要显示的 DOM 树节点存放至少一个节点(隐藏 DOM 元素在渲染树中没有对应节点)。渲染树上的节点称为“框”或者“盒”,符合 CSS 模型的定义,将页面元素看作一个具有填充、边距、边框和位置的盒。一旦 DOM 树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素了。

当 DOM 改变影响到元素的几何属性(宽和高)——例如改变了边框宽度或在段落中添加文字,将发生一系列后续动作——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。不是所有的 DOM 改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。 

总结:

DOM 访问和操作是现代网页应用中很重要的一部分。但每次你通过桥梁从 ECMAScript 岛到达 DOM 岛时,都会被收取“过桥费”。为减少 DOM 编程中的性能损失,请牢记以下几点:

小心地处理 HTML 集合,因为他们表现出“存在性”,总是对底层文档重新查询。将集合的 length 属性缓存到一个变量中,在迭代中使用这个变量。如果经常操作这个集合,可以将集合拷贝到数组中。

注意重绘和重排版;批量修改风格,离线操作 DOM 树,缓存并减少对布局信息的访问。

动画中使用绝对坐标,使用拖放代理。

使用事件托管技术最小化事件句柄数量。

4.循环

新知识点: 达夫设备(用于优化循环性能) 

总结:

for,while,do-while 循环的性能特性相似,谁也不比谁更快或更慢。

除非你要迭代遍历一个属性未知的对象,否则不要使用 for-in 循环。

 改善循环性能的最好办法是减少每次迭代中的运算量,并减少循环迭代次数。

一般来说,switch 总是比 if-else 更快,但并不总是最好的解决方法。

当判断条件较多时,查表法比 if-else 或者 switch 更快。

浏览器的调用栈尺寸限制了递归算法在 JavaScript 中的应用;栈溢出错误导致其他代码也不能正常执行。

 如果你遇到一个栈溢出错误,将方法修改为一个迭代算法或者使用制表法可以避免重复工作。
 运行的代码总量越大,使用这些策略所带来的性能提升就越明显
 
  

5.字符串和正则表达式 

关于正则表达式,之后要进行更深入的学习

1.字符串连接

当连接数量巨大或尺寸巨大的字符串时,数组联合是 IE7 和它的早期版本上唯一具有合理性能的方法。如果你不关心 IE7 和它的早期版本,数组联合是连接字符串最慢的方法之一。使用简单的+和+=取而代之,可避免(产生)不必要的中间字符串。

2.正则表达式工作原理

正则表达式处理的基本步骤:

Step 1: Compilation

第一步:编译 

当你创建了一个正则表达式对象之后(使用一个正则表达式直接量或者 RegExp 构造器),浏览器检查你的模板有没有错误,然后将它转换成一个本机代码例程,用于执行匹配工作。如果你将正则表达式赋给一个变量,你可以避免重复执行此步骤。 

第二步:设置起始位置 

当一个正则表达式投入使用时,首先要确定目标字符串中开始搜索的位置。它是字符串的起始位置,或者由正则表达式的 lastIndex 属性指定

第三步:匹配每个正则表达式的字元
 正则表达式一旦找好起始位置,它将一个一个地扫描目标文本和正则表达式模板。当一个特定字元匹配
失败时,正则表达式将试图回溯到扫描之前的位置上,然后进入正则表达式其他可能的路径上。

第四步:匹配成功或失败 

如果在字符串的当前位置上发现一个完全匹配,那么正则表达式宣布成功。如果正则表达式的所有可能路径都尝试过了,但是没有成功地匹配,那么正则表达式引擎回到第二步,从字符串的下一个字符重新尝试。只有字符串中的每个字符(以及最后一个字符后面的位置)都经历了这样的过程之后,还没有成功匹配,那么正则表达式就宣布彻底失败。 


提高正则表达式效率 

1.关注如何让匹配更快失败 

正则表达式处理慢往往是因为匹配失败过程慢,而不是匹配成功过程慢。如果你使用正则表达式匹配一个很大字符串的一小部分,情况更为严重,正则表达式匹配失败的位置比匹配成功的位置要多得多。如果一个修改使正则表达式匹配更快但失败更慢(例如,通过增加所需的回溯次数去尝试所有分支的排列组合),这通常是一个失败的修改。 

2.正则表达式以简单的,必需的字元开始

3.减少分支的数量,缩小它们的范围
4.将正则表达式赋给变量,以重用它们
5.将复杂的正则表达式拆分为简单的片断

什么时候不应该使用正则表达式 

只是搜索文字字符串时


6. 响应接口

新知识点:Web Workers 

网页工人线程适合于那些纯数据的,或者与浏览器 UI 没关系的长运行脚本。它看起来用处不大,而网页应用程序中通常有一些数据处理功能将受益于工人线程,而不是定时器。 

考虑这样一个例子,解析一个很大的 JSON 字符串(JSON 解析将在后面第七章讨论)。假设数据足够大,至少需要 500 毫秒才能完成解析任务。很显然时间太长了以至于不能允许 JavaScript 在客户端上运行它,因为它会干扰用户体验。此任务难以分解成用于定时器的小段任务,所以工人线程成为理想的解决方案。下面的代码说明了它在网页上的应用:

var worker = new Worker("jsonparser.js");

//when the data is available, this event handler is called

worker.onmessage = function(event){

//the JSON structure is passed back

var jsonData = event.data;

//the JSON structure is used

evaluateData(jsonData); 

};

//pass in the large JSON string to parse

worker.postMessage(jsonText);

工人线程的代码负责 JSON 解析,如下:

//inside of jsonparser.js
//this event handler is called when JSON data is available

self.onmessage = function(event){

//the JSON string comes in as event.data

var jsonText = event.data;

//parse the structure

var jsonData = JSON.parse(jsonText);

//send back to the results

self.postMessage(jsonData);}; 

请注意,即使 JSON.parse()可能需要 500 毫秒或更多时间,也没有必要添加更多代码来分解处理过程。此处理过程发生在一个独立的线程中,所以你可以让它一直运行完解析过程而不会干扰用户体验。 



总结:

JavaScript 和用户界面更新在同一个进程内运行,同一时刻只有其中一个可以运行。这意味着当 JavaScript代码正在运行时,用户界面不能响应输入,反之亦然。有效地管理 UI 线程就是要确保 JavaScript 不能运行太长时间,以免影响用户体验。最后,请牢记如下几点: 

JavaScript 运行时间不应该超过 100 毫秒。过长的运行时间导致 UI 更新出现可察觉的延迟,从而对整体用户体验产生负面影响。 

JavaScript 运行期间,浏览器响应用户交互的行为存在差异。无论如何,JavaScript 长时间运行将导致用户体验混乱和脱节。 

 定时器可用于安排代码推迟执行,它使得你可以将长运行脚本分解成一系列较小的任务。

网页工人线程是新式浏览器才支持的特性,它允许你在 UI 线程之外运行 JavaScript 代码而避免锁定 UI。 

网页应用程序越复杂,积极主动地管理 UI 线程就越显得重要。没有什么 JavaScript 代码可以重要到允

许影响用户体验的程度。 


7.Ajax

注意 跨域、jsonp、 动态标签


8.编程方面的优化

通过避免使用 eval_r()和 Function()构造器避免二次评估。此外,给 setTimeout()和 setInterval()传递函数参数而不是字符串参数。

 创建新对象和数组时使用对象直接量和数组直接量。它们比非直接量形式创建和初始化更快。
 避免重复进行相同工作。当需要检测浏览器时,使用延迟加载或条件预加载。
 当执行数学远算时,考虑使用位操作,它直接在数字底层进行操作。

原生方法总是比 JavaScript 写的东西要快。尽量使用原生方法。


9.开发和部署

开发和部署过程对基于 JavaScript 的应用程序可以产生巨大影响,最重要的几个步骤如下:

合并 JavaScript 文件,减少 HTTP 请求的数量

使用 YUI 压缩器紧凑处理 JavaScript 文件

以压缩形式提供 JavaScript 文件(gzip 编码)

通过设置 HTTP 响应报文头使 JavaScript 文件可缓存,通过向文件名附加时间戳解决缓存问题

使用内容传递网络(CDN)提供 JavaScript 文件,CDN 不仅可以提高性能,它还可以为你管理压缩和缓存、

所有这些步骤应当自动完成,不论是使用公开的开发工具诸如 Apache Ant,还是使用自定义的开发工具以实现特定需求。如果你使这些开发工具为你服务,你可以极大改善那些大量使用 JavaScript 代码的网页应用或网站的性能。 



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值