本文转自life的leanote博客: 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++]); } }
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与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
当DOM的变化影响了某元素的几何属性(高, 宽), 那么浏览器会使受影响的元素失效, 重新渲染树(reflow), 再显示出来(repain)
触发reflow:
DOM添加或修改
DOM尺寸发生变化(border, padding, margin, width, height)
内容发生变化, 比如图片src变了, innerHTML += "lll"
浏览器窗口发生变化
reflow和repain任务会加入UI线程队列, 除非你想得到布局信息, 比如:
offsetTop, offsetLeft
scrollTop, scrollLeft
此时会导致UI线程队列强制刷新, 立刻执行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(target.nodeName == "a") { // 执行方法之 } }
算法和流程控制
数组遍历不要使用for-in
把arr.length缓存起来, 不要每次都取, 因为arr.length是一次属性查找(是一次计算)
要么减少循环次数, 要么减少每次循环的计算量
将概率大的放在最前, 可以使用二分法控制比较的个数
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都有自己的全局运行环境.
有4中方式可以运行字符串:
eval("n1 + n2"); // 不要使用
new Function("arg1", "arg2", "return arg1+arg2"); // 不要使用
setTimeout("n1+n2", 1000); 或 setInterval() => 第一个参数使用function() {}
[], {}比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); // 调用之 }