因为浏览器在遇到<body>标签之前,不会渲染页面的任何部分。
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>标签就可以加载他们 。
<script type="text/javascript" src="file1.js" defer></script>
一个带有 defer 属性的<script>标签可以放置在文档的任何位置。对应的 JavaScript 文件将在<script>被解析时启动下载,但代码不会被执行,直到 DOM 加载完成(在 onload 事件句柄被调用之前)。当一个 defer的 JavaScript 文件被下载时,它不会阻塞浏览器的其他处理过程,所以这些文件可以与页面的其他资源一起并行下载。
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>
函数中局部变量的访问速度总是最快的,而全局变量通常是最慢的
全局变量总是处于运行期上下文作用域链的最后一个位置,所以总是最远才能触及的。
一般来说,一个运行期上下文的作用域链不会被改变。但是,有两种表达式可以在运行时临时改变运行期上下文作用域链。第一个是 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 代码的性能:将经常使用的对象成员,数组项,和域外变量存入局部变量中。然后,访问局部变量的速度会快于那些原始变量。
当浏览器下载完所有页面 HTML 标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据结构: 一棵DOM树表示页面结构,一棵渲染树表示 DOM 节点如何显示。
渲染树中为每个需要显示的 DOM 树节点存放至少一个节点(隐藏 DOM 元素在渲染树中没有对应节点)。渲染树上的节点称为“框”或者“盒”,符合 CSS 模型的定义,将页面元素看作一个具有填充、边距、边框和位置的盒。一旦 DOM 树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素了。
当 DOM 改变影响到元素的几何属性(宽和高)——例如改变了边框宽度或在段落中添加文字,将发生一系列后续动作——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。不是所有的 DOM 改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。
总结:
DOM 访问和操作是现代网页应用中很重要的一部分。但每次你通过桥梁从 ECMAScript 岛到达 DOM 岛时,都会被收取“过桥费”。为减少 DOM 编程中的性能损失,请牢记以下几点:
小心地处理 HTML 集合,因为他们表现出“存在性”,总是对底层文档重新查询。将集合的 length 属性缓存到一个变量中,在迭代中使用这个变量。如果经常操作这个集合,可以将集合拷贝到数组中。
注意重绘和重排版;批量修改风格,离线操作 DOM 树,缓存并减少对布局信息的访问。
动画中使用绝对坐标,使用拖放代理。
使用事件托管技术最小化事件句柄数量。
总结:
for,while,do-while 循环的性能特性相似,谁也不比谁更快或更慢。
除非你要迭代遍历一个属性未知的对象,否则不要使用 for-in 循环。
改善循环性能的最好办法是减少每次迭代中的运算量,并减少循环迭代次数。
一般来说,switch 总是比 if-else 更快,但并不总是最好的解决方法。
当判断条件较多时,查表法比 if-else 或者 switch 更快。
浏览器的调用栈尺寸限制了递归算法在 JavaScript 中的应用;栈溢出错误导致其他代码也不能正常执行。
如果你遇到一个栈溢出错误,将方法修改为一个迭代算法或者使用制表法可以避免重复工作。
运行的代码总量越大,使用这些策略所带来的性能提升就越明显
当连接数量巨大或尺寸巨大的字符串时,数组联合是 IE7 和它的早期版本上唯一具有合理性能的方法。如果你不关心 IE7 和它的早期版本,数组联合是连接字符串最慢的方法之一。使用简单的+和+=取而代之,可避免(产生)不必要的中间字符串。
正则表达式处理的基本步骤:
Step 1: Compilation
第一步:编译
当你创建了一个正则表达式对象之后(使用一个正则表达式直接量或者 RegExp 构造器),浏览器检查你的模板有没有错误,然后将它转换成一个本机代码例程,用于执行匹配工作。如果你将正则表达式赋给一个变量,你可以避免重复执行此步骤。
第二步:设置起始位置
当一个正则表达式投入使用时,首先要确定目标字符串中开始搜索的位置。它是字符串的起始位置,或者由正则表达式的 lastIndex 属性指定
第三步:匹配每个正则表达式的字元
正则表达式一旦找好起始位置,它将一个一个地扫描目标文本和正则表达式模板。当一个特定字元匹配 失败时,正则表达式将试图回溯到扫描之前的位置上,然后进入正则表达式其他可能的路径上。
第四步:匹配成功或失败
如果在字符串的当前位置上发现一个完全匹配,那么正则表达式宣布成功。如果正则表达式的所有可能路径都尝试过了,但是没有成功地匹配,那么正则表达式引擎回到第二步,从字符串的下一个字符重新尝试。只有字符串中的每个字符(以及最后一个字符后面的位置)都经历了这样的过程之后,还没有成功匹配,那么正则表达式就宣布彻底失败。
提高正则表达式效率
1.关注如何让匹配更快失败
正则表达式处理慢往往是因为匹配失败过程慢,而不是匹配成功过程慢。如果你使用正则表达式匹配一个很大字符串的一小部分,情况更为严重,正则表达式匹配失败的位置比匹配成功的位置要多得多。如果一个修改使正则表达式匹配更快但失败更慢(例如,通过增加所需的回溯次数去尝试所有分支的排列组合),这通常是一个失败的修改。
什么时候不应该使用正则表达式
只是搜索文字字符串时
新知识点: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 代码可以重要到允
许影响用户体验的程度。
通过避免使用 eval_r()和 Function()构造器避免二次评估。此外,给 setTimeout()和 setInterval()传递函数参数而不是字符串参数。
创建新对象和数组时使用对象直接量和数组直接量。它们比非直接量形式创建和初始化更快。
避免重复进行相同工作。当需要检测浏览器时,使用延迟加载或条件预加载。
当执行数学远算时,考虑使用位操作,它直接在数字底层进行操作。
原生方法总是比 JavaScript 写的东西要快。尽量使用原生方法。
开发和部署过程对基于 JavaScript 的应用程序可以产生巨大影响,最重要的几个步骤如下:
合并 JavaScript 文件,减少 HTTP 请求的数量
使用 YUI 压缩器紧凑处理 JavaScript 文件
以压缩形式提供 JavaScript 文件(gzip 编码)
通过设置 HTTP 响应报文头使 JavaScript 文件可缓存,通过向文件名附加时间戳解决缓存问题
使用内容传递网络(CDN)提供 JavaScript 文件,CDN 不仅可以提高性能,它还可以为你管理压缩和缓存、
所有这些步骤应当自动完成,不论是使用公开的开发工具诸如 Apache Ant,还是使用自定义的开发工具以实现特定需求。如果你使这些开发工具为你服务,你可以极大改善那些大量使用 JavaScript 代码的网页应用或网站的性能。