本总结是对《高性能 JavaScript》这本书的总结也是记录笔记,加深我对 JavaScript 的认识及实践技巧。
一、脚本的加载和执行
一般来说,JavaScript 代码的执行会阻塞浏览器进行的其他程序,比如用户界面绘制。每次遇到<script>
后,,页面都必须停下来等待代码下载并执行,然后再继续解析和渲染页面。在这期间,页面渲染和用户交互是完全被阻塞的,例如页面出现长时间的白屏。解决方案如下:
- 将
<script>
放在</body>
之前,确保脚被执行前页面已完成渲染。 <script>
标签越少越好,可以考虑用 gulp 任务合并。因为 HTTP 请求会带来额外的性能开销。- 内嵌脚本如果在
<link>
之后会导致页面阻塞去等待样式表的下载。这样能获得最精准的样式信息,但是会阻塞其他任务。(不建议,但 hack 除外) -
使用无阻塞下载的方式
/**
* @title: addTags动态加载js标签
* @params:
* tagUrl: js资源的数组
* eachLoadedCB: 每个资源加载完毕的回调
* allLoadedCB: 所有资源加载完毕的回调
* */
function addTags(tagUrl, eachLoadedCB, allLoadedCB) {
if (!(tagUrl instanceof Array)) {
alert("first arguments must be array of urls");
return false;
}
var totalResource = tagUrl.length;
var restResource = totalResource;
var downLoadPercent;
for (var i = 0, len = tagUrl.length; len > i; i++) {
//标签对象
var _TagObjs;
if (/.js$/.test(tagUrl[i])) {
_TagObjs = document.createElement("script");
_TagObjs.setAttribute('type', 'text/javascript');
_TagObjs.setAttribute('src', tagUrl[i]);
head.appendChild(_TagObjs);
}
if(_TagObjs.readyState){
//IE
_TagObjs.onreadystatechange = function () {
if(_TagObjs.readyState == "loaded" || _TagObjs.readyState == "complete"){
_TagObjs.onreadystatechange = null;
restResource--;
downLoadPercent = Math.round((totalResource - restResource) / totalResource * 100);
!!eachLoadedCB && eachLoadedCB(totalResource, restResource, downLoadPercent);
!!!restResource && allLoadedCB && allLoadedCB();
}
}
}else{
_TagObjs.onload = function () {
restResource--;
downLoadPercent = Math.round((totalResource - restResource) / totalResource * 100);
!!eachLoadedCB && eachLoadedCB(totalResource, restResource, downLoadPercent);
!!!restResource && allLoadedCB && allLoadedCB();
};
}
}
}
/**
* 使用
* */
addTags(["core.js","lib.js"],function (t,r,d) {
alert("total:"+t+",rest:"+r+",percent:"+d);
},function () {
alert("all loaded")
})
二、数据存储
JavaScript 的四种基本的数据存取位置,比较简单,这里只是简单列举:
- 字面量 (最快)
- 本地变量 (var 创建)(最快)
- 数组元素 (最慢)
- 对象成员(最慢)
作用域链
找了一张图:
- 函数执行都会创建自己的执行环境,且执行环境都有自己的作用域链,用于解析标识符。函数运行时的变量对象会放在作用域链的顶端。当执行环境被销毁,此对象也随之销毁。故,在函数执行过程中,每遇到变量,都会经历一次标识符解析过程以决定从哪儿获取存储数据。---> 藏的越深,找的越慢!
- 作用域链的末尾是全局变量对象,故搜索该变量的过程必须遍历整个作用域链!一般是讲此全局变量缓存的到局部变量中。
function init(){
var a = document;
var b = a.body;
b.getElementByTagName("a");
}
- 改变作用域链的方式有: - with(不建议使用,可使用局部变量替换) - try catch (可用)
try 内函数的作用域对象放在首位,catch 内函数的作用域对象放在第二位。catch 执行完毕,作用域链就返回到之前的状态。建议将 catch 内的处理逻辑由专门的函数处理,由于只有一条执行语句,且没有局部变量的访问,作用域链的临时改变不会影响代码性能。
try{
method();
} catch(e){
handleErr(e);
}
闭包、作用域和内存
- 闭包会造成更多的内存开销
- 闭包会放在作用域链的顶端,第二层是活动对象的作用域,也就是说,闭包的存在会频繁的出现跨作用域访问标识符的情况,每次访问都会带来性能损失,解救方案:将常用的跨作用域的变量存储到局部变量中,然后直接访问局部变量。
原型链
- Javascript 中的对象是基于原型的。
- 所有的对象都是 Object 的实例。
- 实例继承了原型链中的所有成员,故原型决定了实例的类型!
浏览器中的原型链结构:
- 嵌套的对象成员会显著的影响性能,访问的速度也越慢,例如:
window.location.href.toString() > window.location.href > location.href - 缓存对象的成员,如果多次访问的话。
a.href;
....
- 属性和方法越深,访问的速度也越慢
三、DOM 编程
- 减少 DOM 的访问次数,把运算留在 JS 端处理。
- 遍历数组的速度快于遍历集合是速度,故将 HTML 集合转化为数组在进行处理会更高效。
function toArray(coll){
for(var i = 0,a = [],len = coll.length;i<len;i++){
a[i] = coll[i];
}
return a;
}
var coll = document.getElementsByTagName("div");
var ar = toArray(coll);
- 建议使用 querySlectorAll()方法
重绘和重排
- 重排:reflow,重新计算元素几何属性,重新构造渲染树,代价很高;
- 重绘: repaint,代价相对较小。
故:
- 批量修改 DOM
- 批量改变样式(ele.style.cssText = "")
- 使元素脱离文档流,避免大部分文档重排: - 动画元素绝对定位,脱离文档流
事件委托
大量元素绑定相同处理事件的时候,将事件绑定到 document 上,通过冒泡事件捕获并处理
四、算法和流程控制
- for, while, do- while,性能相当,灵活使用
- 避免使用 for-in,除非遍历对象的数量不确定。
- switch 和 if-else 灵活使用
- 出现栈溢出,可能是因为使用了递归,将递归改为迭代可以避免。因为运行一个循环比反复调用一个函数的开销要少的多。
五、字符串和正则表达式
- 使用+和+=操作字符串,避免不必要的中间字符串
- 正则,待续
六、快速响应用户界面
- js 任务不可超过 100ms
- 使用 settimeout 将长时间的 js 放入队列中异步执行,不阻碍 ui 进程。
- 可以使用 web works
七、AJAX
- 减少请求次数
八、编程实践
- 避免双重求值(使用 eval()和 Function()运行字符串代码)
- Object 和 Array 使用直接量,而不是 new
- 环境判断的函数应该使用延迟加载技术(Lazy Loading)或者条件预加载技术(Conditional Advance Loading),即,环境确定后,用该环境下的函数替换主函数
//延迟加载技术(Lazy Loading)
//第一次执行判断环境,之后修改addHandler函数并执行
functin addHandler(x){
if(isThisEnvironment){
addHandler = function(x){}
}else{
addHandler = function(x){}
}
addHandler(x);
}
//条件预加载技术(Conditional Advance Loading)
//脚本加载期间检测,并修改addHandler函数;
var addHandler = isThisEnvironment?function(x){}:function(x){};
- 使用 js 原生提供的方法(Math)
- 使用位操作
九、构建高性能 js 应用
- 合并多个 JavaScript 文件,减少 HTTP 请求数
- 非必须的脚本延迟加载
- JavaScript 压缩,方法:ugify、js 重构(GCC)
- 启用 gzip
- manifest 缓存技术,包含 manifest 的 html 也会被缓存
- CDN
- 文件名增加 MD5 值防止缓存更新问题
10、工具
- 分类:性能分析+网络分析
- 浏览器的开发工具
- Fidder/postman/Charles Proxy
- 本文作者:烈风裘
- 本文题目:高性能JavaScript实践总结
- 本文链接:https://xiangst0816.github.io/blog/gao-xing-neng-nai-JavaScript-shi-jian/
- 版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!