此书主要从js语法层面,算法,浏览器渲染机制,网络传输HTTP、XHR,项目创建和部署方面给与高性能网站的优化方式。
一、加载和运行
1.javascript阻塞dom渲染,尽量放在body底部。可以组合外接javascript,数量多性能更慢。 保持javascript文件小。//window.onload之后开始下载脚本。 不改变dom的脚本使用defered属性,其会异步下载,并不阻塞js执行,在dom完全渲染完再执行,defer在window.onload之前。
2.async是在下载完,defer是在dom准备完毕。(HTML4中的规范,目前无兼容问题)
3. script中动态插入script,会异步下载执行。动态加载脚本是非阻塞js下载常用的模式,可以跨浏览器。 通过创建XHR请求,动态创建script也可以实现非阻塞。(不常用)
4.剩余js代码压缩并放在在/body之前
二、数据访问
1.性能对比:直接量>变量性能更快>对象>数组 (目前浏览器影响较小)
2.内部【scope】属性包含一个函数被创建的作用域中对象的集合。决定哪些数据可被访问,函数运行时,查看执行上下文。函数运行时,每个标示符都要经过作用于链的搜索过程,这个查找过程会影响性能。
函数中局部变量的访问速度总是最快的,而全局变量通常是最慢的。
对策:用局部变量代替复用的全局变量
场景:try catch的时候,catch函数中所有局部变量会放在第二个作用于对象中,执行完恢复。with会产生新的作用域
3.闭包可以访问外函数的变量和全局变量,容易导致内存泄露,所以小心使用。
4.js对象基于原型链,调用深层的原型链上的属性或者方法会使性能下降。(目前浏览器影响较小)
成员嵌套越深,访问越慢。
以局部变量替代属性,避免多余的属性查找带来性能开销。在处理嵌套对象成员时这点特别重要,它们会对运行速度产生难以置信的影响。
三、DOM
你通过桥梁从
ECMAScript
岛到达
DOM
岛时,都会被收取
“
过桥费
”
- 最小化 DOM 访问,在 JavaScript 端做尽可能多的事情。
- 在反复访问的地方使用局部变量存放 DOM 引用.
- 小心地处理 HTML 集合,因为他们表现出“存在性”,总是对底层文档重新查询。将集合的 length 属性缓存到一个变量中,在迭代中使用这个变量。如果经常操作这个集合,可以将集合拷贝到数组中。
- 使用速度更快的 API,诸如 querySelectorAll()
- 重绘和重排版;批量修改风格,离线操作 DOM 树,缓存并减少对布局信息的访问
- 动画中使用绝对坐标,使用拖放代理。
- 事件托管技术最小化事件句柄数量。
四、算法和流程控制
- Loop循环(迭代): for、while、do while、for in 。其中for in会访问对象及其原型上的属性,速度上慢了50%,适合对数目不详的对象属性进行操作。 改善循环: 1.将循环终结条件存入局部变量 2.减少循环次数
- 条件: if else 适合数量少的条件判断,而 switch适合数量较多的条件判断。 优化if else 将可能性最大的放在前。 如何键值可以判断的条件,则可以使用数组或对象查表法。
- 递归: 所有递归能实现的都可以用迭代来实现,优先使用迭代,因为迭代的循环开销要比递归的函数反复调用的开销小。大的递归可能造成stack overflow(栈溢出),因此可以用迭代改写。 递归的好处是逻辑比迭代的易懂。。
附:(合并排序算法) 将每个需要排序的数组拆分为两个数组进行排序,得到两个有序的数组就可以得到需要的结果,递归进行(1,2,4,8……),直到所拆的数组只剩下一项。
function merge(left, right){
var result = [];
while (left.length > 0 && right.length > 0){
if (left[0] < right[0]){result.push(left.shift());
} else {result.push(right.shift());
}}
return result.concat(left).concat(right);
}
function mergeSort(items){if (items.length == 1) {
return items;}
var middle = Math.floor(items.length / 2),
left = items.slice(0, middle),
right = items.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
递归优化: 减少多次调用的重复工作,建立缓存
function memfactorial(n){
if (!memfactorial.cache){
memfactorial.cache = {"0": 1,
"1":
};
}
if (!memfactorial.cache.hasOwnProperty(n)){
memfactorial.cache[n] = n * memfactorial (n-1);
}
return memfactorial.cache[n];
}
当遇到代码总量越大的时候,这些优化的用处越大
五、字符串和正则表达式
方式:字符串相加, array.join(), str.concat('',’’);
在大数量的字符串相加的情况下,使用数组方法实现字符串相连的效率更高,因为单纯的字符串相加包含了拷贝越来越大的字符串这个过程。; 使用concat方法的优势是比较灵活,缺点性能不如+
正则表达式的匹配流程: 1.找到起始位置 2.匹配正则表达式的每一个单元 3.成功或者失败回溯
正则中贪婪版本: 的* 表示0到正无穷,尽可能多的内容
懒惰版本: *?表示尽可能少的内容
[\s\S]表示所有的字符包括换行符,不能用.代替
/<html>[\s\S]*?<head>[\s\S]*?<title>[\s\S]*?<\/title>[\s\S]*?<\/head>[\s\S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>/
正则优化思想:减少回溯
六、相应接口
浏览器进程的队列处理机制。。。
- 大多数浏览器有一个单独的处理进程,它由两个任务所共享: JavaScript 任务和用户界面更新任务。每个时刻只有其中的一个操作得以执行,也就是说当 JavaScript代码运行时用户界面不能对输入产生反应,反之亦然。这些任务不是运行 JavaScript 代码,就是执行 UI 更新,包括重绘和重排版。大多数浏览器在 JavaScript 运行时停止 UI 线程队列中的任务,也就是说 JavaScript 任务必须尽快结束,以免对用户体验造成不良影响,如果长时间未响应,浏览器会触发无法相应弹出框。
单一的js应当在100毫秒内相应用户的输入,若超出100ms,用户就不能感受到对接口的控制。
为了让出对UI的控制,js定时器进入了视野。
windows上的定时器分辨率为15ms,值为15的定时器延时将会根据最后一次系统时间刷新转化为0或者15. 一般建议最小为25ms
- 如果单端js执行时间太长,可以分解成若干个步骤存在一个数组中。通过定时器的异步原理,可以不阻塞UI的渲染。
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);
}
- web worker可以让js代码运行不占用浏览UI进程的时间。
(Web work的运行环境:
一个浏览器对象,只包含四个属性:
appName, appVersion, userAgent,
和
platform
一个
location
对象(和
window
里的一样,只是所有属性都是只读的)
一个
self
对象指向全局工人线程对象
一个
importScripts()
方法,使工人线程可以加载外部
JavaScript
文件
• All ECMAScript objects, such as
Object
,
Array
,
Date
, etc.
所有
ECMAScript
对象,诸如
Object
,
Array
,
Data
,等等。
• The
XMLHttpRequest
constructor
XMLHttpRequest
构造器
setTimeout()
和
setInterval()
方法
close()
方法可立即停止工人线程)
webwork需要单独建立一个js文件,
要创建网页工人线程,你必须传入这个
JavaScript
文件的
URL
:
var worker = new Worker("code.js”);
worker.onmessage = function(event){
alert(event.data);
};
worker.postMessage("Nicholas”);
//inside code.js
self.onmessage = function(event){
self.postMessage("Hello, " + event.data + "!");
};
消息系统是页面和工人线程之间唯一的交互途径。
应用场景:
网页工人线程适合于那些纯数据的,或者与浏览器
UI
没关系的长运行脚本。它看起来用处不大,而网页应用程序中通常有一些数据处理功能将受益于工人线程,而不是定时器。例如解析json即json.parse(…),编码或解码字符串, 复杂数学运算,给一个大数组排序,等超过100ms且不适合用定时器分解。
Ps: chrome浏览器不支持本地的情况下使用webwork, 需要架在服务器上, firefox支持。
七. AJAX异步js和XML
1.如果请求不改变服务器状态只是取回数据(又称作幂等动作)则使用
GET
。
GET
请求被缓冲起来,如果你多次提取相同的数据可提高性能。只有当 URL 和参数的长度超过了 2'048 个字符时才使用 POST 提取数据。因为 Internet Explorer 限制 URL的长度,过长将导致请求(参数)被截断。
2.可以通过动态创建script并添加src属性,无法获得报头,数据而且必须在一个回调函数中组装。 由于是script执行,速度比XMR快
3.减少HTTP请求
4.
Beacons
灯标 新建image对象,添加src
5.传输类型: json(快) json-p(快) 字符串自定义分隔符 数组形式(去除属性的对象)最快。 jsonp它将数据视为可运行的 JavaScript 而不是字符串,解析速度极快。它能够跨域使用,但不应涉及敏感数据
6.快速渲染的两种形式: 服务端组装好HTML传给客户端直接渲染,服务端穿json给客户端执行逻辑渲染
7.js字符串操作很慢,但字符串的split操作非常快
8.缓存: 最好的XHR优化就是不发出XHR,1.在服务器端,设置 HTTP 头,确保返回报文将被缓存在浏览器中。 2. 在客户端,设置一个 Expires 头,于本地缓存已获取的数据,不要多次请求同一个数据。
如果你希望 Ajax 响应报文能够被浏览器所缓存,你必须在发起请求时使用 GET 方法
9.CDN优化。
八.最佳实践
1.不要使用eval或Function,setTimeout等通过传入字符串产生函数
2.使用数组或者对象字面量来创建,速度非常快
3.避免进行重复的操作
4.可以的情况下,进行位操作的方式来处理数字,因为它是在原生层面产生作用的
5.尽量使用js的原生api,因为它是在c++低级语言上编译的,速度比任何js封装的代码快的多。
九.构建和部署
1.合并js文件,减少HTTP请求的数量
2.YUI 压缩器减少js代码大小,压缩形式提供js文件(gzip)
3.设置HTTP头可缓存,通过文件名附加时间戳控制
4.使用CDN
十.学会使用各种浏览器(chrome,firefox,safari,IE等)的调试