Chapter 1: 加载与运行
在很多浏览器里,渲染界面的线程和执行JavaScript的是同一个线程,所以当执行JS的时候,浏览器就停下任何其他任务。不论是遇见内联的JS代码,还是外部加载的JS文件,浏览器都只能暂时放下跟界面有关的工作去执行JS代码。
这样设计的原因是JS代码会修改DOM,比如通过document.write()。
====================================================================
脚本的位置
结论:放在body的底端。
原因:不会阻止页面被渲染,这样会带来一个比较好的用户体验。
另外,在多数浏览器里,JS脚本的下载已经是并行的了,但是执行不确定,作者没有提到。另外下载JS脚本会阻止其他类型的文件被下载。
====================================================================
将script标签分组
script标签越少越好,不论是内联的脚本还是外部文件。
如果是外部文件的话,减少文件数可以减少HTTP请求次数。
====================================================================
非阻滞性脚本
这节主要说的是延迟加载JS文件的策略。
延迟脚本
支持的浏览器很少,略过。
动态脚本元素
就是通过DOM方法动态创建一个<script>元素,再插入到文档中。
- 与正常的<script>脚本不同,不论将初始化下载的代码放在文档的哪里,它的下载都不会阻碍其他进程的处理;
- 一般来讲把它放在<head>里会安全些,因为否则IE可能会有异常;
- 有些浏览器里,通过这种方式下载的新脚本加载后就会立即执行,在有些浏览器里则会等到其他动态生成的脚本运行完成再执行,这个行为值得详细探究下;
- 它加载完成后会有一个load事件触发,可以侦听这个事件做一些后续处理,IE对这个机制的处理有些不同。
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("head")[0].appendChild(script);
}
使用方式:
loadScript("file1.js", function() {
loadScript("file2.js", function() {
loadScript("file3.js", function() {
alert("All files are loaded!");
});
});
});
通过XMLHttpRequest注入脚本
先用XMLHttpRequest异步加载一个JS文件的内容,再把其内容插入到动态创建的<script>里面。
- 它也不会阻碍其它进程;
- 同时加载文件内容后并不导致立即执行,所以程序员可以控制执行时机;
- 唯一的缺陷是,被加载的文件必须与原网页属于相同域名。
常用方案
有一种模式被经常用来处理这个问题,就是在页面里加载一点必须的初始JS代码,然后再用这段代码加载其余所需的代码。比如下边这样:
<script type="text/javascript" src="loader.js"></script>
<script type="text/javascript">
loadScript("the-rest.js", function(){
Application.init();
});
</script>
另外有一些第三方的JS库可以用来完成这个效果:
- 雅虎的YUI;
- Ryan Grove写的LazyLoad,它还可以控制执行顺序;
- LABjs,它的创造者正是[You Don't Know JS]系列的作者:Kyle Simpson,这套库可以更细致地控制下载执行顺序以及依赖关系。
Chapter 5: 字符串与正则表达式
====================================================================
字符串连接
主要是由于市面上浏览器太多,版本一直在演进,结论太杂乱,好像基本上没有什么定论。有四种方法连接字符串:
+
+=
array.join()
string.concat()
基本上前面两个比后面的块。
将+与+=混合起来的做法,比如:
str += "one" + "two";
不如下面的做法:
str += "one";
str += "two";
和:
str = str + "one" + "two";
因为后面的做法避免了创建过多的临时字符串,注意最后的方法里,str必须放在等号右边表达式里的最左端。
str = String.prototype.concat.apply(str, array);
====================================================================
正则表达式优化
====================================================================
字符串修剪
作者介绍的方法很多,大多是基于正则表达式的方法,但这个问题似乎也可以略过了,现在多数浏览器都支持原生态的trim()函数了。参加下面的链接:
W3CSchool也增加了关于trim()的页面。
Chapter 6: 响应界面
====================================================================
浏览器的用户界面线程
执行JS代码的线程同时也负责更新UI,而这个单一线程同时只能处理一项事务,JS代码的执行会阻碍UI的更新,反之亦然。浏览器将所有的任务都存放在一个队列里,顺序处理,正在在执行JS代码的时候有可能会暂停把新事件添加到队列中。
浏览器对执行时间的限制
各个浏览器对于JS脚本的执行时间都有限制,只是具体时长不同。一旦超出这个限制,就会弹出错误窗口。
多久算久
一般来说,一段JS代码执行不超过0.1秒算合理。
====================================================================
用定时器交出控制权
如果JS运行的时间实在太长,为了不影响用户体验,只能将任务分割,通过定时器手动延时,从时间上分开处理不同的子任务。
定时器
就是setInterval()和setTimeout()。
- 定时器的好处是它会重置浏览器对时间限制的计算,所以通过定时器处理的任务的整体时间是可以超过这个限制的;
- 定时器所指定的延时到达以后,会将指定的新任务插入到队列中,并不是说就保证立刻执行该任务;
- setInterval()并不重复插入它已经出入到队列中的任务。
精确度
各个浏览器都会有向前或向后的几毫秒的时差。Windows的定时器执行周期是15毫秒,所以作者建议传给定时器的延时不要低过25秒。
用定时器处理数组
判断一段代码是否可以异步处理的两个标准:
- 是否一定要同步完成?
- 是否一定要顺序完成?
用定时器分时处理一个数组:
var todo = items.concat(); //create a clone of the original
setTimeout(function() {
//get next item in the array and process it
process(todo.shift());
//if there's more items to process, create another timer
if (todo.length > 0) {
setTimeout(arguments.callee, 25);
} else {
callback(items);
}
}, 25);
用函数封装一下:
function processArray(items, process, callback) {
var todo = items.concat(); //create a clone of the original
setTimeout(function() {
process(todo.shift());
if (todo.length > 0) {
setTimeout(arguments.callee, 25);
} else {
callback(items);
}
}, 25);
}
怎么用:
var items = [123, 789, 323, 778, 232, 654, 219, 543, 321, 160];
function outputValue(value) {
console.log(value);
}
processArray(items, outputValue, function() {
console.log("Done!");
});
分割任务
将一个函数的逻辑切割开来,分别放在小的函数里,再用上面处理数组的模式处理:
function saveDocument(id) {
var tasks = [openDocument, writeText, closeDocument, updateUI];
setTimeout(function() {
//execute the next task
var task = tasks.shift();
task(id);
//determine if there's more
if (tasks.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}
再封装下:
function multistep(steps, args, callback) {
var tasks = steps.concat(); //clone the array
setTimeout(function() {
//execute the next task
var task = tasks.shift();
task.apply(null, args || []);
//determine if there's more
if (tasks.length > 0) {
setTimeout(arguments.callee, 25);
} else {
callback();
}
}, 25);
}
怎么用:
function saveDocument(id) {
var tasks = [openDocument, writeText, closeDocument, updateUI];
multistep(tasks, [id], function() {
alert("Save completed!");
});
}
被延时的代码
也不是说处理数组时,就每隔25毫秒处理一个元素就是最佳效果,事实肯定不是这样。既然合理的执行时间是0.1秒,那么在分割任务时,保证每个子任务不要超过这个限制就好,而作者建议从实践上把这个时限调整到0.05秒更好。
其实可以通过用动态计算处理时间的方式来优化前面处理数组的方法:
function timedProcessArray(items, process, callback) {
var todo = items.concat(); //create a clone of the original
setTimeout(function() {
var start = +new Date();
do {
process(todo.shift());
} while (todo.length > 0 && (+new Date() - start < 50));
if (todo.length > 0) {
setTimeout(arguments.callee, 25);
} else {
callback(items);
}
}, 25);
}
非数组的情况下怎么办作者就没说了。
定时器与效率
如果同时对多处代码使用了定时器,那么定时器之间会产生竞争,这样反而可能拖慢效率。另外定时器在时间上的尺度也有影响,延时大于1秒的定时器相对稳定,但小于0.1秒就会令性能出现瓶颈。
====================================================================
工作线程
- 在主线程以外开新线程来执行任务,所以不用担心会阻碍主线程的处理;
- 源自HTML5,但是分离出来自成一体;
- JS的运行环境是独立的,不再与主页面的JS环境有任何直接关系,所以能访问到的资源很有限,代码也必须是从独立的JS文件里加载;
- 分离的JS运行环境所支持的功能也有限制;
- 相互通讯依靠事件处理机制,传递数据的格式也有限制;
- 适用于与UI无关的纯数据处理的复杂逻辑。
Chapter 7: Ajax
数据传输
请求数据
- XHR
- 动态插入script标签
- 多部件XHR
另外还有两个叫iframe和Comet,但是因为罕见作者略过了。
XHR
- 能更精确地控制数据传输,甚至能streaming;
- 缺点是相同域名限制;
- 传回数据只能是字符串或者XML,所以处理较慢;
- 一般情况下,只是获取数据的话用GET,因为能够被浏览器缓存;
- 除非URL太长,超过2048个字符,才会用POST;
- 要修改服务器端状态的请求用POST;
动态插入script标签
- 对于HTTP请求没有那么多控制,像头数据;
- 只有GET方法可选,没有POST;
- 传回来的数据直接当做JS代码运行,所以性能好,但不支持JSON或者XML;
- 没有域名的限制,但也带来安全隐患;
多部件XHR
- 可以将多个甚至是不同类型文件(如:CSS,图片,HTML片段)打包到一个HTTP请求传输;
- 与streaming结合起来用,效果更佳;
- 请求的文件不能够被浏览器缓存;
- IE不支持streaming;
发送数据
只发送数据而不考虑获取的话,有两个选择:
- XHR
- Beacons
XHR
- 只发送数据的话,GET快一些;
Beacons
- 通过一个Image对象的src属性,以query string的形式传送参数回服务器;
- 不支持POST;
- 性能高;
====================================================================
数据格式
- XML
- JSON
- HTML
- 定制格式
略过以上格式讨论的细节
结论
总的来说,JSON或者自定义的分割字符串格式是效率最好的。
====================================================================
Ajax效能指南
缓存
在服务器端通过HTTP头设定缓存
就是设定这个参数:
Expires: Mon, 28 Jul 2014 23:30:00 GMT
当然,在不同的技术平台上,语法不同。
手动用JS代码缓存
就是用一个Object作为字典存下获取的数据。
了解你使用的Ajax库的局限
由于JS库通常要统一化不同浏览器的操作,所以它们会减少所支持的Ajax功能,像streaming这样的高级功能可能就不会被支持。