高性能javascript
TRANSCRIPT
High � Performance � JavaScript2011-09-09
11年9月13日星期二
About � Me
谢传贵(阿贵)
前端开发部-前端架构组
新浪微薄@十月光风
11年9月13日星期二
11年9月13日星期二
先来看一场微电影,时长6.5s
11年9月13日星期二
0s
11年9月13日星期二
0.5s
11年9月13日星期二
1.0s
11年9月13日星期二
1.5s
11年9月13日星期二
2.0s
11年9月13日星期二
2.5s
11年9月13日星期二
3.0s
11年9月13日星期二
3.5s
11年9月13日星期二
4.0s
11年9月13日星期二
4.5s
11年9月13日星期二
5.0s
11年9月13日星期二
5.5s
11年9月13日星期二
6.0s
11年9月13日星期二
6.5s
11年9月13日星期二
0.0 0.5 1.0 1.5
2.0 2.5 3.0 3.5
4.0 4.5
6.5
5.55.0
6.0
回顾
11年9月13日星期二
知己知彼,百战不殆
11年9月13日星期二
Topic
JavaScript运行过程
快速响应UI
更多编程实践
效率相关的工具
11年9月13日星期二
JavaScript运行的过程是怎么样的?
11年9月13日星期二
解释(interpreted)
编译(compiled)
从源码到可执行代码
11年9月13日星期二
(function(){alert(‘hello � world’)
})
0100100101010101
Runtime.exec(‘hello.exe’)
source.js(源码)
helloworld.exe(二进制码)
运行时环境
编译
编译
执行
11年9月13日星期二
(function(){alert(‘hello � world’)
})
Runtime.exec(‘hello.exe’)
source.js(源码)
helloworld.exe(二进制码)
运行时环境
解释
编译&运行
11年9月13日星期二
(function(){alert(‘hello � world’)
})
Runtime.exec(‘中间机器
解释
编译&运行
运行时环境(浏览器)解释器执行伪代码
11年9月13日星期二
(function(){alert(‘hello � world’)
})
Runtime.exec(‘中间机器
解释
编译&运行
运行时环境(浏览器)
JavaScript � Engine
11年9月13日星期二
JavaScript � 是解释型语言
11年9月13日星期二
JavaScript代码执行的过程
词法分析
语法分析
预编译
运行
语法检查
运行时
11年9月13日星期二
JavaScript代码执行的过程
词法分析
语法分析
预编译
运行
语法检查
运行时
将上下文中var申明 � 的变量放入”栈”中 � 并赋值为undefined
读入”定义”的函数
11年9月13日星期二
看个例子
11年9月13日星期二
(function(){alert(a); � // � 会报错么?if(false){
var � a=1; � }
})();
11年9月13日星期二
函数执行前,函数内部变量均被声明(function(){
alert(a); � // � 显示undefined,不报错if(false){
var � a=1; � //不会执行到,亦被声明}
})();
11年9月13日星期二
再来看个例子
11年9月13日星期二
test();function � test(){ � alert(1);}test();function � test(){ � alert(2);}test();var � test � = � function(){ � alert(3);}test();test � = � function(){ � alert(4);}test();
11年9月13日星期二
test();function � test(){//预编译定义,运行时略过 � alert(1);}test();function � test(){//预编译定义,运行时略过 � alert(2);}test();var � test � = � function(){//预编译声明,运行时赋值 � alert(3);}test();test � = � function(){//运行时,变量赋值 � alert(4);}test();
11年9月13日星期二
JavaScript性能会影响到用户体验吗?
11年9月13日星期二
JavaScript � 性能已经直接影响到用户体验
11年9月13日星期二
JavaScript � is � Slow!
11年9月13日星期二
11年9月13日星期二
11年9月13日星期二
JavaScript � is � Fast!
11年9月13日星期二
JavaScript引擎
V8 � C++
JaegerMonkey � C++
JScript � JSctipt.Net
Nitro � C++
Karakan � C++
...
11年9月13日星期二
http://ie.microsoft.com/testdrive/benchmarks/sunspider/default.html
JavaScript运行越来越快
11年9月13日星期二
IE8 IE6 Sougou IE7 IE9Tt Chrome Maxyhon Firefox TheworldSe360 Other Opera Safari
http://www.1688.com/11年9月13日星期二
IE840.60%
IE639.81%
Sougou5.03%
3.74%3.09%
1.67%1.24%1.17%1.11%0.99%0.96%0.37%0.13%0.10%
IE8 IE6 Sougou IE7 IE9Tt Chrome Maxyhon Firefox TheworldSe360 Other Opera Safari
http://www.1688.com/11年9月13日星期二
但如果不了解,不注意,依然有可能写出非常低效(Inefficient)的JavaScript代码
11年9月13日星期二
Douglas � Crockford Steve � Sounders Nicholas.C.Zakes
大牛11年9月13日星期二
UI线程
11年9月13日星期二
Input output<!DOCTYPE � html>
<html><head>
<title>1688首页</title></head><body>
<div>hello</div><script>
doc.write(‘helloworld’);</script><div>world</div>
</body></html>
浏览器是如何渲染的?
?
11年9月13日星期二
Fetch Parse Flow Paint
URL Cache Tree DisplayList
Pixels
浏览器渲染过程
http://technotes-himanshu.blogspot.com/2010/05/html-dom.html11年9月13日星期二
Flow
Event
Paint
Script
执行脚本过程中的渲染
11年9月13日星期二
JS执行和浏览器渲染
11年9月13日星期二
<!DOCTYPE html><html>
<head><title>1688首页</title>
</head><body>
<div id=”a”>hello</div><script>
doc.write(‘helloworld’);</script><div id=”b”>world</div></body>
</html>
构建DOM 树
document
head
title
body
div
script
div
textNodeJS引擎创建了
textNode
11年9月13日星期二
阻塞:JS运行会中断HTML的渲染
11年9月13日星期二
浏览器是单线程作业同一时刻只能做一件事情,要么是用户界面更新,要么是JavaScript脚本执行
11年9月13日星期二
在UI线程忙碌的情况下,UI更新和JS执行都会被加入UI队列里,直到当前任务执行完毕,才会被激活
11年9月13日星期二
UI � Rendering � Threadtime
DOM
1.构建DOM
2.更新 � UI
11年9月13日星期二
UI � Rendering � Threadtime
DOM
渲染出此时的DOM
UI � Update
11年9月13日星期二
UI � Rendering � Threadtime
DOM
UI � Update Exec � JS
3.JS � 脚本新增DOM节点
11年9月13日星期二
UI � RenderingThreadtime
DOM
UI � Update Exec � JS UI � Update
4.更新UI
11年9月13日星期二
JavaScript执行时间 � = � UI不可响应时间
11年9月13日星期二
UI � Update Exec � JS UI � Update
time
UI可响应的
11年9月13日星期二
UI � Update Exec � JS UI � Update
time
UI不可响应的
假死
11年9月13日星期二
JavaScript执行过程耗时越久,浏览器等待响应用户输入的时间就越长,
就有多不好的用户体验
11年9月13日星期二
浏览器对于JavaScript运行的限制
1.调用栈大小限制
2.长时间运行限制
主要分为两类:
11年9月13日星期二
�
��� ���, ��� �
11年9月13日星期二
�
-� ���
11年9月13日星期二
浏览器限制
IE:500万条语句
Firefox:10秒
Safari:5秒
Chrome:没有单独的长运行脚本限制,替代做法是依赖于其崩溃检测系统来处理此类问题
Opera:没有长运行脚本限制
11年9月13日星期二
多久才算太久?
11年9月13日星期二
如果JavaScript运行了整整几秒钟,那么很可能你做错了什么....
--Brendan � Eich
11年9月13日星期二
在100毫秒以内响应用户输入,用户会认为自己在直接操作界面中的对象.超过100毫秒意味着用户会感到自己与界面失去联系.
-- � Jokob � Nielsen
11年9月13日星期二
佳实践:少于50毫秒
11年9月13日星期二
JavaScript Load最佳实践
11年9月13日星期二
浏览器在进行JavaScript下载,解析和运行都是会阻塞页面渲染的?
11年9月13日星期二
<!DOCTYPE html><html>
<head><title>1688首页</title>
</head><body>
<div>hello</div><script src=”jQuery.js”></script><div>world</div>
</body></html>
11年9月13日星期二
UI � Update JavaScript UI � Update
结果
11年9月13日星期二
UI � Update jQuery.js UI � Update
结果
11年9月13日星期二
UI � Update UI � Updatedownload parse run
结果
11年9月13日星期二
<!DOCTYPE html><html>
<head><title>1688首页</title>
</head><body>
<div>hello</div><script src=”jQuery.js”></script><div>world</div><script src=”yui2.js”></script><div>1688</div><script src=”yui3.js”></script>
</body></html>
11年9月13日星期二
UI � Update JavaScript UI � Update
结果
JavaScript UI � Update JavaScript
11年9月13日星期二
JavaScript Load最佳实践1:把JS放到页面底部
11年9月13日星期二
11年9月13日星期二
UI � Update JavaScriptUI � Update
结果
JavaScriptUI � Update JavaScript
11年9月13日星期二
<!DOCTYPE html><html>
<head><title>1688首页</title>
</head><body>
<div>hello</div><div>world</div><div>1688</div><script src=”jQuery.js”></script><script src=”yui2.js”></script><script src=”yui3.js”></script>
</body></html>
11年9月13日星期二
JavaScript Load最佳实践2:JS合并
11年9月13日星期二
<!DOCTYPE html><html>
<head><title>1688首页</title>
</head><body>
<div>hello</div><div>world</div><div>1688</div><script src=”jQuery-yui2-yui3.js”></script>
</body></html>
11年9月13日星期二
UI � Update UI � Update
结果
JavaScriptUI � Update
11年9月13日星期二
当然我们可以通过工具来完成自动合并
11年9月13日星期二
独角兽
Google � Page � Speed � Module
...
11年9月13日星期二
JavaScript Load最佳实践3:无阻塞异步加载JS
11年9月13日星期二
可选方案
Script DOM Element
Script Defer
Script Async
Iframed JS
XML HttpRequest Script Injection
11年9月13日星期二
Script DOM Element原理var script = document.createElement("script"),body = document.body; script.type = "text/javascript"; script.src = "foo.js"; body.insertBefore(script, body.firstChild);
11年9月13日星期二
UI � Update UI � Updaterun
结果
download parse
Time
11年9月13日星期二
类库选择YUI2(YAHOO.util.Get.script)
jQuery( jQuery.ajax)
LABjs
headjs
requirejs
SeaJs
...
11年9月13日星期二
Script � Defer
<script � defer � src="foo.js"></script>
11年9月13日星期二
浏览器支持情况
7.0 3.5 4.0 ? 5.0
11年9月13日星期二
Script � Async
<script � async � src="foo.js"></script>
11年9月13日星期二
浏览器支持情况
7.0 3.5 ? ? 5.0
11年9月13日星期二
Iframed � JSfunction � iframedJS(s){ �
document.write("<iframe � id= � 'i'></iframe>"); � var � d � = � document.getElementById("i").contentWindow.document; � d.write('<!doctype � html><html><body><scr' � + � 'ipt � src="'+s
+'"></scr' � + � 'ipt></body></html>'); � window.setTimeout((function(){d.close();}),0);}
11年9月13日星期二
对同一个iframe多次进行doc.open+write+close,会增加浏览历史记录
Firefox � doc.write � iframe至页面,可能不能马上取到其引用
domain的潜在问题
缺点
11年9月13日星期二
XML � HttpRequest � Script � Injectionvar � xhr � = � new � XMLHttpRequest();xhr.open(‘get’,‘file.js’,true);xhr.onreadystatechange=function(){
if(xhr.staus==4){if(xhr.status>=200&& � xhr.status<300|| � xhr.status==304){
var � script � = � document.createElemet(‘script’);script.type= � ‘text/javascript’;script.text � = � xhr.responseText;document.body.appendChild(script);
}}
}
11年9月13日星期二
特点
可先下载,但不立即执行主流浏览器都支持有同域的要求
11年9月13日星期二
JavaScript � Compression � 佳实践
11年9月13日星期二
JavaScript � Compression � 佳实践1.上线前压缩
11年9月13日星期二
可选工具
YUI � Compressor
Google � � Closure � Compiler
UglifyJS
Packer
...
11年9月13日星期二
JavaScript � Compression � 佳实践2.网络传输压缩
11年9月13日星期二
可选方案
gzip
compress
deflate
identity
11年9月13日星期二
JavaScript � Exec � 佳实践
11年9月13日星期二
JavaScript � Exec � 佳实践 � 1:使用定时器让出时间片
11年9月13日星期二
UI � Thread
UI � Update-Button JavaScript-click JavaScript-click
0 50
UI � Update-Button
JavaScript-click
JavaScript-clickTimer � coder
UI � Queue
setTimeout() called Timer code queued
11年9月13日星期二
使用定时器处理数组var � todo � = � items.concat();
setTime(function(){//取得数组的下个元素进行处理process(todo.shift());
if(todo.length � > � 0){setTimeout(arguments.callee,25);
} � else � {callback(items);
}},25);
11年9月13日星期二
分割任务function � multistep(steps,args,callback){
var � tasks � = � steps.concat();setTimeout(function(){
var � task � = � tasks.shitf();task.apply(null,args � || � []);
if(tasks.length � > � 0){setTimeout(arguments.callee,25);
}else{callback();
}},25);
}11年9月13日星期二
JavaScript � Exec � 佳实践 � 2:Web � Workers
11年9月13日星期二
11年9月13日星期二
Web � Workers � 没有绑定UI线程
11年9月13日星期二
//in � pagevar � worker � = � new � Worker("process.js"); � worker.onmessage � = � function(event){
useData(event.data);worker.postMessage(values);
};
//in � process.jsself.onmessage � = � function(event){ �
var � items � = � event.data; � for � (var � i=0,len=items.length; � i � < � len; � i++){};process(items[i]); � self.postMessage(items);
}11年9月13日星期二
使用场景
编码/解码大字符串
复杂数学运算
大数组排序
11年9月13日星期二
浏览器支持情况
7.0 3.5 ? 10.6 4.0
11年9月13日星期二
DOM编程
11年9月13日星期二
天生就慢
ECAM � Land DOM � Land
ECMA每次访问DOM,都需要途经这座桥,并交纳”过桥费”.访问的次数越多,费用就越高
11年9月13日星期二
代价很昂贵
function � innerHTMLLoop(){for(var � count=0;count<1000;i++){
document.getElementById(‘i’).innerHTML+=”a”;
}}
11年9月13日星期二
优化后
function � innerHTMLLoop(){var � content=[];for(var � count=0;count<1000;i++){
content.push(‘a’);}document.getElementById(‘i’).innerHTML � = � content.join(‘’);
}
11年9月13日星期二
比较
innerHTML,createElement,cloneNode
案例:http://blog.stevenlevithan.com/archives/faster-than-innerhtml
http://stevenlevithan.com/demo/replaceHtml.html
11年9月13日星期二
重绘和重排
11年9月13日星期二
UI � Update时间 � = � UI不可用时间
11年9月13日星期二
<button � id="btn" � style="font-size: � 30px;">Click � Me</button><script � type="text/javascript"> �
window.onload � = � function(){document.getElementById("btn").onclick � = � function(){ �
var � div � = � document.createElement(“div”); � div.className � = � “tip”; � div.innerHTML � = � “You � clicked � me!”; � document.body.appendChild(div);
}; � }
</script>
11年9月13日星期二
JavaScript执行会触发重绘和重排会导致长时间UI � Update
11年9月13日星期二
浏览器渲染
11年9月13日星期二
重绘何时发生?
visibility
颜色
背景图片
不会触发layout改变
11年9月13日星期二
重排何时发生?
添加或删除可见的DOM节点
元素位置改变
元素尺寸改变(包括:外边框,内边距,边框厚度,宽度,高度等属性改变)
页面渲染器初始化
浏览器窗口尺寸改变
页面布局+几何属性改变都会触发重排
11年9月13日星期二
重绘<button � id="btn">Click � Me</button><script � type="text/javascript"> �
window.onload � = � function(){document.getElementById("btn").onclick � = � function(){ �
this.style.color � = � "#ff0";}
}; � </script>
重绘
11年9月13日星期二
重排
<button � id="btn" � style="font-size: � 30px;">Click � Me</button><script � type="text/javascript"> � window.onload � = � function(){
document.getElementById("btn").onclick � = � function(){ � var � div � = � document.createElement(“div”); � div.className � = � “tip”; � div.innerHTML � = � “You � clicked � me!”; � document.body.appendChild(div);
}; � }</script>
重排
11年9月13日星期二
小化重绘和重排
11年9月13日星期二
方法
批量修改样式,采用cssTest或者改变className的方式
批量修改DOM,使DOM脱离文档
缓存布局信息
让元素脱离动画流
事件委托
...
11年9月13日星期二
批量修改样式
var el = document.createElementById(‘mydiv’);el.style.borderLeft = ”1px”;el.style.borderRight = ‘2px’;el.style.padding = ‘5px’;
重绘
重绘
重绘
3次重绘
11年9月13日星期二
优化后
var el = document.createElementById(‘mydiv’);el.style.cssText = ‘border-left:1px;border-right:2px;padding:5px;’;
1次重绘
11年9月13日星期二
还可以这么做
var el = document.createElementById(‘mydiv’);el.className = ‘active’;
1次重绘
11年9月13日星期二
使DOM脱离文档
隐藏元素,应用修改,重新显示
使用文档片段(document � fragment)在当前DOM之外构造一个子树,在把它拷回文档
将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素
11年9月13日星期二
1.隐藏元素var � ul � = � document.getElementById(‘myList’);
ul.style.display � = � ‘none’;
appendDataToElement(ul,data);
ul.style.display � = � ‘block’;
11年9月13日星期二
文档片段
var � fragment � = � document.createDocmentFragment();
appendDataToElement(fragment,data);
document.getElementById(‘myList’).appendChild(‘f
ragment’)
11年9月13日星期二
缓存布局信息
myElement.style.left � = � 1+myElement.offsetLeft
+’px’;
if(myElement.offsetLeft>500){
stopAnimation();
}
非常的低效
11年9月13日星期二
可以这么优化
current++
myElement.style.left � = � current+’px’;
if(myElement.offsetLeft>500){
stopAnimation();
}
11年9月13日星期二
让元素脱离动画流
使用绝对位置定位页面上的元素,将其脱离文档流
让元素动起来.当它扩大时,会临时覆盖部分页面.这个时候只会小区域的重绘
当动画结束时恢复定位,从而知会下移一次文档的其他位置
11年9月13日星期二
事件委托
场景:当页面中存在大量元素,而且每⼀一个都要⼀一次或多次绑定事件处理器.
11年9月13日星期二
事件委托
结果是可能会影响性能.每绑定一个事件处理器都是有代价的,它要么加重了页面负担.,要么增加了运行期的执行时间.需要访问和修改的DOM元素越多, � 应用程序也就越慢.
11年9月13日星期二
解决方案
事件冒泡
例:YAHOO.util.Event.delegate
http://developer.yahoo.com/yui/event/
11年9月13日星期二
更多编程实践
更高效的代码(条件预加载,延迟加载,位操作等)
正则表达式优化(例:Detail详情延迟加载)
数据缓存(JS对象缓存,Ajax数据缓存等)
GC和避免内存泄漏(http://www.aliued.cn/2010/09/19/gc-and-js-memory-leak.html)
CSS3动画
其他见(Extreme � JavaScript � Performance.pdf)
11年9月13日星期二
工具
JS执行效率测试工具
内存泄漏检测工具
...
11年9月13日星期二
�
�����F
谢谢11年9月13日星期二