高性能javascript总结

一、加载和执行

问题起源

多数浏览器使用单线程来处理用户界面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(火狐浏览器除外)

  1. 字面量(对象、数组、函数、正则、字符串、数字、布尔、null、undefined)
  2. 本地变量(var)
  3. 数组元素
  4. 对象成员(还要访问原型,遍历原型链。通过将第一次查找的值放在局部变量中改善)

闭包引起的性能问题

问题

Function同对象一样,拥有可以编程访问的属性,和一系列不能通过代码访问而仅供javascript引擎存取的内部属性。其中一个叫[[scope]]
闭包的[[scope]]属性包含了与执行环境作用域链相同的对象引用,因此会产生副作用。

  • 函数在执行时,会有执行环境,会创建一个包含变量以及其他数据的活动对象,当执行完毕,函数的活动对象会销毁。
  • 引入闭包后,函数的活动对象会[[scope]]属性中,所以活动对象没有办法销毁。
  • 闭包执行时,会创建一个执行环境,还有有一个闭包自身创建的活动对象。

解决

执行完后,将闭包设为null
将常用的跨作用域的变量存储在局部变量中。

三、DOM编程

修改DOM问题

用脚本对DOM操作代价很昂贵,DOM和javascript两个相互独立的功能通过接口彼此连接,就会消耗。两个岛屿,相连接就需要收过桥费。那么,如果修改dom代价更高,怎么减少代价呢

解决

  1. 用局部变量存储修改的内容。
  2. 减少dom访问次数,把运算放在javascript这一端。
  3. innerHTML和document.createElement 相差无几,如果更新一大段HTML推荐使用innerHTML。除谷歌等基于Webkit内核的浏览器外,一般是innerHTML效率高。

HTML集合问题

document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()

会产生一个HTML集合,类似数组的列表,只有length属性,但是HTML集合一直与文档保持连接,每次更新时,都会重复执行查询过程,哪怕只是获取元素个数。

解决

  1. 将集合拷贝到普通数组中a
function toArray(coll){
 for(var i=0;a=[];len=coll.length;i<len;i++){
 a[i]=coll[i];
 }
 return a;
}
  1. 访问集合元素时使用局部变量,把len缓存在局部变量中。
  2. 使用选择器API querySelectAll()/querySelect(), 使用css选择器作为参数并返回一个NodeList,返回的节点是静态的,不会和文档连接、

获取DOM元素问题和解决

属性名(元素节点) 被替代的属性(包括其他空白、注释节点 不推荐)
children childnodes
childElementCount childnodes.length
firstElementChid firstChild
lastElementChild lastChild
nextElementSibling nextSibling
previousElementSibling previousSibling

重排和重绘问题

重排:几何属性变化时,重新构造渲染树

  1. 添加删除DOM
  2. 元素位置变化
  3. 元素尺寸变化
  4. 内容改变
  5. 页面渲染器初始化
  6. 浏览器窗口大小改变

重绘:重排完成后,重新绘制受影响的部分到页面。

解决

避免使用获取布局信息的操作。例如scrollHeight等,因为会刷新渲染队列,可以将布局信息存储在局部变量中。
应该合并多次DOM和样式的操作,一次性处理掉。

 /1.cssText属性*/
document,getElementById('#mydiv').style.cssText='height:500px;width:200px;border-left:1px;'`
/2.*修改css的class名称,但是需要检查级联样式*/

对DOM元素进行一系列操作时

  1. 使元素脱离文档流
  2. 对其应用多重改变
  3. 把元素带回文档
//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外其他循环性能差不多,除非你要遍历一个属性未知的对象。

解决

  1. 使用局部变量保存length
  2. 减少迭代的运算量,倒序查找

流程问题

一些 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的应用的可靠性,提升性能,通过向地理位置最近的用户传输内容,减少网络延迟。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章