一、加载和执行
问题起源
多数浏览器使用单线程来处理用户界面UI刷新和Javascript脚本的执行,所以同一时刻只能做一件事。javascript脚本霸道地让页面等待自己执行完毕。
解决办法
html4中的规范指出<script>
可以放在文档的head或者body中。理论上说将样式文件和脚本文件放在head中有助于页面的渲染和交互。
<html>
<head>
<title></title>
<script src="files.js"></script>
<script src="files1.js"></script>
<script src="files2.js"></script>
<link type="text/css" href="styles.css">
</head>
</html>
记住!浏览器在解析到<body>
之前,不会渲染页面的任何部分,把脚本放在页面的顶部有将会导致明显的延迟。
-
因此,推荐将所有的
<script>
放在<body>
标签的底部,以减少对整个页面下载的影响。 -
减少页面外链脚本文件的数量将会改善性能,可以将多个文件合并成一个,只需要用一个
<script
标签。把部分文件和代码合并成一个外链文件。 -
无阻塞加载脚本,异步加载javascript文件。window的onload时间触发后再下载脚本。
- 给
<script>
添加defer或者async属性,都是并行下载。 - async在加载完后立即执行。
- defer在页面加载完后执行(onload触发前),并且会按顺序执行。
- 给
-
动态脚本元素,通过document.createElement(“script”)来实现页面不阻塞。
- 其他浏览器可以通过scrip标签接收完成时触发一个load事件,通过监听此事件来获得脚本加载完成时的状态。
var script=document.createElement("script"); script.onload=fucntion(){ alert('script loaded') }
-
IE,它会触发一个readystatechange事件
<script>
元素提供一个readystate属性,在外链文件下载过程中,它的值会不断变化,有5种取值。var script=document.createElement("script"); script.onreadystatechange=function(){ if(script.readyState == 'loaded'||script.readyState == 'complete'){ script.onreadystatechange=null; alert('Script loaded') } }
-
通过XHR脚本注入实现不阻塞。
- 你可以下载js代码但不立即执行
- 但是js文件必须与所请求的页面处于相同的域。意味着不能从CDN中下载js文件,很少使用。
//1.创建一个xhr对象
var xhr=new XMLHttpRquest();
//2.下载js文件
xhr.open('get','files.js',true);
xhr.onreadystatechange=function(){
if(xhr.readyState == 4){
if(xhr.status >= 200 && xhr.status <300 || xhr.status == 300){
var script=document.createElement("script");
script.type='text/javascript';
script.text=xhr.responseText;//响应的数据
//3.代码注入到页面
document.body.appendchild(script);
}
}
};
xhr.send(null);
二、数据存取
问题起源
通过改变数据的存储位置来获得最佳的速写性能。
解决
多使用局部变量。它在作用域链的起始位置。
javascript中有四种基本的数据存储位置。
速度:1==2>3>4(火狐浏览器除外)
- 字面量(对象、数组、函数、正则、字符串、数字、布尔、null、undefined)
- 本地变量(var)
- 数组元素
- 对象成员(还要访问原型,遍历原型链。通过将第一次查找的值放在局部变量中改善)
闭包引起的性能问题
问题
Function同对象一样,拥有可以编程访问的属性,和一系列不能通过代码访问而仅供javascript引擎存取的内部属性。其中一个叫[[scope]]
闭包的[[scope]]属性包含了与执行环境作用域链相同的对象引用,因此会产生副作用。
- 函数在执行时,会有执行环境,会创建一个包含变量以及其他数据的活动对象,当执行完毕,函数的活动对象会销毁。
- 引入闭包后,函数的活动对象会[[scope]]属性中,所以活动对象没有办法销毁。
- 闭包执行时,会创建一个执行环境,还有有一个闭包自身创建的活动对象。
解决
执行完后,将闭包设为null
将常用的跨作用域的变量存储在局部变量中。
三、DOM编程
修改DOM问题
用脚本对DOM操作代价很昂贵,DOM和javascript两个相互独立的功能通过接口彼此连接,就会消耗。两个岛屿,相连接就需要收过桥费。那么,如果修改dom代价更高,怎么减少代价呢
解决
- 用局部变量存储修改的内容。
- 减少dom访问次数,把运算放在javascript这一端。
- innerHTML和document.createElement 相差无几,如果更新一大段HTML推荐使用innerHTML。除谷歌等基于Webkit内核的浏览器外,一般是innerHTML效率高。
HTML集合问题
document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()
会产生一个HTML集合,类似数组的列表,只有length属性,但是HTML集合一直与文档保持连接,每次更新时,都会重复执行查询过程,哪怕只是获取元素个数。
解决
- 将集合拷贝到普通数组中a
function toArray(coll){
for(var i=0;a=[];len=coll.length;i<len;i++){
a[i]=coll[i];
}
return a;
}
- 访问集合元素时使用局部变量,把len缓存在局部变量中。
- 使用选择器API querySelectAll()/querySelect(), 使用css选择器作为参数并返回一个NodeList,返回的节点是静态的,不会和文档连接、
获取DOM元素问题和解决
属性名(元素节点) | 被替代的属性(包括其他空白、注释节点 不推荐) |
---|---|
children | childnodes |
childElementCount | childnodes.length |
firstElementChid | firstChild |
lastElementChild | lastChild |
nextElementSibling | nextSibling |
previousElementSibling | previousSibling |
重排和重绘问题
重排:几何属性变化时,重新构造渲染树
- 添加删除DOM
- 元素位置变化
- 元素尺寸变化
- 内容改变
- 页面渲染器初始化
- 浏览器窗口大小改变
重绘:重排完成后,重新绘制受影响的部分到页面。
解决
避免使用获取布局信息的操作。例如scrollHeight等,因为会刷新渲染队列,可以将布局信息存储在局部变量中。
应该合并多次DOM和样式的操作,一次性处理掉。
/1.cssText属性*/
document,getElementById('#mydiv').style.cssText='height:500px;width:200px;border-left:1px;'`
/2.*修改css的class名称,但是需要检查级联样式*/
对DOM元素进行一系列操作时
- 使元素脱离文档流
- 对其应用多重改变
- 把元素带回文档
//demo1
//使用隐藏元素
var ul=document.getElementById('#mydiv');
ul.style.display='none';
//应用修改
修改方法
//重新显示
ul.style.display='block';
//demo2
//在DOM之外构建并更新文档片段,
var fragment=document.creatDocumentFragment();
应用方法
//然后再拷贝回去
document.getElementById('#mydiv').appendChild(fragment)
//demo3
//拷贝原始节点,修改副本,用新的替换旧的
var old=document.getElementById('#mydiv');
var clone=old.cloneNode(true);
应用方法
old.parentNode.replaceChild(clone,old);
使用事件委托减少事件处理器的数量。
四、循环和流程控制
循环问题
一般循环也很消耗性能,除for in外其他循环性能差不多,除非你要遍历一个属性未知的对象。
解决
- 使用局部变量保存length
- 减少迭代的运算量,倒序查找
流程问题
一些 if else 和switch等流程
解决
较少时 使用if else 多时使用switch
查找表,比两者都快,将返回的结果存到数组中
递归问题
递归可以把复杂的算法变得简单,但是终止条件不明确或者缺少终止条件会导致函数长时间运行,也会遇到浏览器的调用栈大小的限制。(每个浏览器都有调用栈的大小,例如谷歌 21837)
解决
验证终止条件
终止条件没有问题,那就是算法中包含太多层的递归。
改用迭代(优化后的循环)
五、快速响应用户界面
浏览器响应页面性能问题
再也没有比点击网页毫无动静令人沮丧了!大多数浏览器让一个单线程共用于执行javascript和更新用户页面,每时刻只能执行一种操作。
javascript和更新用户界面的进程通常被称为“浏览器UI线程”,任务会存在队列中,直到进程空闲,一旦进程空闲,下一个任务就重新被放在进程中执行。
浏览器限制
浏览器限制了javascript任务的运行时间,避免某些恶意代码能不停止的操作用户的浏览器和计算机。
-
调用栈大小限制
-
长时间运行脚本限制:浏览器会记录脚本的运行时间,并达到一定限度时终止它。火狐是10秒,IE是500万条语句。
- 多久算久:单个javascript操作花费总时不超过100ms
通过定时器分解解决
- 通过定时器让出UI线程,使得页面得到更新。
- setTimeout(fn,200);200表示设置200毫秒后定时器代码加入队列,从setTimeout调用开始算。只有当创建它的函数执行完后,定时器代码才可能被执行。
- 通过定时记录代码的运行时间,避免将任务分解为过于零碎的片段。
- 同一时间只有一个定时器存在,结束后才会创建另一个,通过这种方法使用定时器不会导致性能问题。
Web Worker解决UI线程
自javascript诞生以来,还没有办法在浏览器UI线程外运行代码呢。web worker 引用一个接口,能使代码运行且不占用浏览器UI线程的时间。并且每个新的worker都在自己的线程中运行,不会影响浏览器UI和其他worker中运行的代码。
worker的通信
通过事件接口通信。网页代码通过postmessage(data)方法给Worker传数据。worker通过onmessage()接收数据。postmessage传递的可以是基本数据类型和object、array。
实际应用
worker适合处理纯数据或者与浏览器UI无关长时间运行的脚本。
解析Json字符串
//页面
var worker=new Worker('file.js');//引入在worker中运行的代码
//5.worker传来对象的event.data进行处理
worker.onmessage=function(event){
var jsonData=event.data;//json字符串解析后的json对象
}
//1.页面通过postmessage向worker中传递json字符串
worker.postmessage(jsonText);
/*Worker负责解析*/
//file.js内部代码
//2.worker 用自己的onmessage处理接收的字符串,即event.data
self.onmessage=function(){
var jsonText=event.data;
//3.开始解析 时间长
var jsonData=JSON.parse(jsonText);
//4.回传页面
self.postmessage(jsonData);
}
六、ajax的请求、发送
ajax是高性能javascript的基础,它通过异步的方式在服务器和浏览器之间传输数据。
那么在与服务器通信时,哪种技术最好呢,又应该用哪种数据格式呢?
请求数据技术
请求数据技术 | 优缺点 |
---|---|
XHR | 优:可以获取响应头信息和设置请求头信息;缺点:不能跨域,服务器传回来的数据会被当成字符串或者XML对象,处理数据很慢 。POST GET send(null) |
动态脚本注入 | 可以跨域,不能设置请求头,只有GET方式,脚本执行,所以响应数据要放在callback里,不安全 |
Multipart XHR | 允许用户只用一个HTTP请求 就可以从服务器端向客户端传送多个资源,通过将js、css、图片在服务器打包,按照双方约定的字符串分割的长字符串传给客户端,用javascript处理这个字符串。缺点:获取的资源不能被浏览器缓存 |
ajax最快的请求就是没有请求:
在服务器端,设置HTTP头信息以确保你的响应会被浏览器缓存。例如设置expires的过期时间。如果遇到版本升级,可以通过给文件增加一个版本号来解决缓存问题。
在浏览器端,把获取的信息存储到本地,从而避免再次请求。
发送数据技术
技术 | 优缺点 |
---|---|
XHR | GET更快 一个TCP包、post适合发送大量的数据 send(params) |
数据格式
格式 | 优缺点 |
---|---|
XML | 及其冗长,解析耗费精力,通用性强 |
JSON | 轻量易于解析 |
HTML | 请求数据转化成HTML响应在页面,繁琐,传输量大 |
CDN
使用内容分发网络CDN,它负责传递内容给终端用户。增强web的应用的可靠性,提升性能,通过向地理位置最近的用户传输内容,减少网络延迟。