您的位置:首页 > Web前端 > JavaScript

高性能javascript

2014-04-30 11:40 495 查看
本文转自lifeleanote博客: http://leanote.com/blog/view/53564ad01a91080383000000

参考 <高性能javascript> 这绝对是一本好书!!

无阻塞

js脚本放在html末尾
通过loadScript()加载与页面渲染无关的脚本, 延迟加载

作用域链

js的作用域是通过函数来决定的, 不存在if() {var a = ""};等其它{}封装的变量只能{}内用, a不是局域作用域.
有一点需要注意: 函数作用域的嵌套关系是定义时决定的, 而不是调用时决定的, 也就 是说,JavaScript 的作用域是静态作用域, 又叫词法作用域,这是因为作用域的嵌套关系可 以在语法分析时确定, 而不必等到运行时确定。下面的例子说明了这一切:
var scope = 'top';
var f1 = function() {
console.log(scope);
};
f1(); // 输出 top
var f2 = function() {
var scope = 'f2';
f1();
};
f2(); // 输出 top


局部变量最快, 最往外速度越慢, 所以可以将常用的全局变量缓存到局部变量中
执行一个函数, 它的作用域链如下:



函数执行过程中, 每遇到一个标识符, 都会从作用域链从头往尾找, 直到搜索为止或没找到.
优化点:

缓存常用全局变量, 比如document

function initUI() {
var doc = document; // 缓存之
var bd = doc.body,
links = doc.getElementsByTagName("a"),
i= 0,
len = links.length;
while(i < len){
update(links[i++]);
}
}


改变作用域链 with

function initUI(){
var a = "local";
with (document) { // 下面的作用域变了, [0]指向了document下的所有对象, 而a到了[1]了
links = getElementsByTagName("a"),
i= 0,
len = links.length;
}
}




try catch也可以改变作用域

闭包, 作用域和内存

闭包的作用: 保存上下文环境
var generateClosure = function() {
var count = 0;
var get = function() {
cunt ++; 6
return count;
};
return get; // 返回函数, 也包括函数所在的环境!
};
var counter = generateClosure();
console.log(counter()); // 输出 1
console.log(counter()); // 输出 2
console.log(counter()); // 输出 3


闭包不但包括被返回的函数,还包括这个函数的定义环境
function assignEvents(){
var id = "xdi9592";
// 闭包
document.getElementById("save-btn").onclick = function(event){
saveDocument(id);
};
}


当assignEvents()运行时作用域链:



闭包的副作用: assignEvents()运行结束后, 活动对象不会销毁, 因为闭包的作用域链还有一份引用!! 所以闭包需要更多的内存开销!!
当闭包执行时, 作用域链如下:



DOM

处理DOM代价很高

浏览器将DOM与javascript独立实现, 所以javascript操作DOM会跨过一条沟, 所以慢!
var a = document.getElementsByTagName("div");
Object.prototype.toString.call(a);
"[object HTMLCollection]"


HTMLCollection 是一个接口,表示 HTML 元素的集合,它提供了可以遍历列表的方法和属性。
HTML DOM 中的 HTMLCollection 是“活”的;如果基本的文档改变时,那些改变通过所有 HTMLCollection 对象会立即显示出来。
所以, 下面代码是个死循环:
var ps = document.getElementsByTagName("p");
for(var i = 0; i < ps.length; ++i) { // 每次都会重新计算length
document.body.appendChild(document.createElement("p"));
}


reflow重排和repain重绘

reflow后发生repain
当DOM的变化影响了某元素的几何属性(高, 宽), 那么浏览器会使受影响的元素失效, 重新渲染树(reflow), 再显示出来(repain)
触发reflow:

DOM添加或修改

DOM尺寸发生变化(border, padding, margin, width, height)

内容发生变化, 比如图片src变了, innerHTML += "lll"

浏览器窗口发生变化

reflow和repain任务会加入UI线程队列, 除非你想得到布局信息, 比如:

offsetTop, offsetLeft

scrollTop, scrollLeft

此时会导致UI线程队列强制刷新, 立刻执行reflow和repain

最小化reflow和repain

一次性改变属性, 不要一点点改!
css("border-left", "1px");
css("width", "2px")


上面就执行了两次reflow
可以一次性将属性全改, 或通过改变class来改变样式(推荐)
可以先隐藏DOM, 修改完后再显示
或创建一个备份, 备份改完了, 替换之前的
var old = document.getElementById("ee");
var n = old.cloneNode(true);
n.appendChild(...);
old.parentNode.replaceChild(n, old); // 替换之


事件委派

避免过多的event bind, 减少内存, 浏览器会追踪每个事件处理器, 会占用很多内存.
event delegation的机制就是通过父来控制子, 事件冒泡到父, 再判断target.nodeName是否是子元素
p.onclick = function(e) {
e = e || windows.e;
var target = e.target || e.srcElement;
if
3ff0
(target.nodeName == "a") {
// 执行方法之
}
}


算法和流程控制

循环

数组遍历不要使用for-in

把arr.length缓存起来, 不要每次都取, 因为arr.length是一次属性查找(是一次计算)

要么减少循环次数, 要么减少每次循环的计算量

if-else

将概率大的放在最前, 可以使用二分法控制比较的个数

字符串和正则

字符串连接

str += "a" + "b" // 内存创建一个临时字符串 t = "ab" 再与str相加
str = str + "a" + "b" // 不必创建临时字符串, 直接加到str后面, 所以该方法效率高 [本地优化]
或以下和上面一样的高效
str += "a"
str += "b"

如果使用
str = "a" + str + "b" 就要创建临时字符串了


浏览器会尝试将左侧的字符串分配更多的内存便于连接其它字符串.
其它的方法, 比如, 都比上面的方法慢!!
["a", "b"].join("");
str.concat("a").concat("b")


正则优化

if(!String.prototype.trim) { // 如果浏览器已经支持了就不要自己写了, 原生的效率肯定高
String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/, "");
}
}


避免使用|, 这样会有过多的选择分支
快速响应用户界面
UI线程运行了 javascript和UI渲染, 是单线程, 一次只能运行一种任务, 该线程维护一个队列, 用户的点击, 交互都是一种任务放置到队列中.
当运行javascript程序过长时, 此时UI线程只能等待该任务完成才能响应其它任务, 此时阻塞了UI的渲染.



当javascipt程序运行过长时, 可以使用setTimetout()来分解任务, 比如分出10个任务, 当运行第一个任务完后再setTimeout(function() {}, 0)第二个任务, 此时该任务会加到队列中, 此时UI线程可以响应用户的请求.
function go(arr) {
setTimetout(function() {
process(arr.shift()); // 处理第一个数组

// 如果没处理完, 再处理
if(arr.length > 0) {
go(arr);
}
}, 10);
}


HTML5解决方案
使用WEB Workers
每个Web Worker都有自己的全局运行环境.

编程实践

避免使用eval运行字符串程序

有4中方式可以运行字符串:
eval("n1 + n2"); // 不要使用
new Function("arg1", "arg2", "return arg1+arg2"); // 不要使用
setTimeout("n1+n2", 1000); 或 setInterval() => 第一个参数使用function() {}

使用Object/Array直接量[]和{}

[], {}比new Array()和new Object()速度快, 代码量少

不要重复工作, 避免重复判断

比如 javascript的on和unbind:
function on(target, e, callback) {
if(!target || !e || !callback) return;
if(target.addEventListener) target.addEventListener(e, callback, false);
else target.attachEvent("on" + e, callback); //IE
}
function unbind(target, e, callback) {
if(!target || !e || !callback) return;
if(target.removeEventListener) target.removeEventListener(e, callback, false);
else target.detachEvent("on" + e, callback);
}


这里每次调用都要浏览器兼容性情况, 比较好的方法好下:
on = window.addEventListener ?
function(target, e, callback) {
target.addEventListener(e, callback, false);
}:
function(target, e, callback) {
target.attachEvent("on" + e, callback);
}


或使用延迟决定
function on(target, e, callback) {
if(!target || !e || !callback) return;
if(target.addEventListener) {
on = function(target, e, callback) { // 覆盖原方法
target.addEventListener(e, callback, false);
}
} else { // IE
on = function(target, e, callback) {
target.attachEvent("on" + e, callback);
}
}
on(target, e, callback); // 调用之
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript 高性能