高性能js一书总结

各个章节的总结

  • js文件和代码位置尽量放在页面最后
  • 减少全局变量,多用字面量和局部变量
  • 闭包中经常访问跨作用域对象建议保存在局部变量中
  • innerHTML一般优于document.creatElement(),特别是在老浏览器
  • 尽量减少使用获取HTML元素集合的方法
  • 在老版本浏览器中,使用nextsibling比childNodes(也是HTML元素集合)查找元素快
  • 使用新的queryselectAll获取的是数组元素,不是HTML元素集合所以性能消耗会比较少
  • 使用获取DOM位置信息的属性或者使用getComputedStyled方法获取计算属性都会触发回流,导致页面的重新计算与排版
  • 多次css属性的修改最好使用cssText或者class属性来修改
  • DOM的批量修改应当先让元素脱离文档流,在修改,再将元素带回文档流

js文件位置相关

  • 由于js的单线程限制,所以尽量将js文件和代码放在body标签之后,避免由于加载执行js代码造成的浏览器假死

数据存储

  • 尽量使用字面量和局部变量,相对于数组和对象属性访问,前者更快
  • 函数作用域相关
    • 当一个函数被创建时,就会创建一个该函数的执行环境,每个执行环境都有自己的作用域链
    • 每个作用域链中初始化当前运行函数的[[scope]]属性中对象,其中的值按照他们在函数中出现的顺序
    • 当以上过程完成是,一个活动对象就创建完成,包含函数运行的所有变量、参数等变量
    • 当函数执行完毕时,执行环境和活动对象都会销毁
    • 也就不难解释循环打印变量为什么是最后一次的值,因为标志位是对象属性,为引用对象,会随着改变而改变
    • 所以全局变量在最顶端的作用域链,查找比较耗时
    • 因此当在函数内部需要多次使用全局变量时,请将这个全局变量存储在局部变量里
function initUI(){
var bd = docuemnt.body,
links = document.getElementsByTagName("a"),
i=0,
len = links.length;
document.getElementById('go-btn').onclick = function(){

}
}

这段代码就用了三次document,因为是全局变量搜索过程相对于局部变量就更加耗时,所以应当将document存储为函数的局部变量
  • 闭包相关
    • 闭包虽然方便,但是可能会导致函数的活动对象无法被回收,引发内存泄露相关问题,需要在使用之后手动置空
    • 早期的IE中,因为DOM不是js的原生对象,所以比较容易引发内存泄露的问题

DOM

  • 每一次通过js和DOM进行交互都会消耗性能
  • DOM相关优化

    • innerHTML一般优于document.creatElement(),特别是在老浏览器
    • 使用获取元素HTML集合的几个方法会消耗大量的性能,因为HTML与文档一直保持连接,会不断的刷新获取,每一次访问都是重新获取document.getelementbytagname classname等获取元素集合的方法
    • 在老版本浏览器中,使用nextsibling比childNodes(也是HTML元素集合)查找元素快
    • 使用新的queryselectAll获取的是数组元素,不是HTML元素集合所以性能消耗会比较少
    • 重绘与回流
    • 两棵树 —— DOM树用来表示页面结构,渲染树用来表示DOM节点如何显示
    • 使用获取DOM位置信息的属性或者使用getComputedStyled方法获取计算属性都会触发回流,导致页面的重新计算与排版
    • 多次css属性的修改最好使用cssText或者class属性来修改
    • DOM的批量修改应当先让元素脱离文档流,在修改,再将元素带回文档流
    • 对于会触发回流的属性应当进行缓存,不要重新获取

算法和流程控制控制

  • 相对于for,while。for…in循环会慢一些
  • 当循环复杂度为O(n)时,每次减少迭代的工作量是最有效的,当循环复杂度大于O(n)时,减少迭代的次数是最有效的。
  • 当循环次数较多时,可以使用达夫设备(Duff’s Device)来减少循环次数
  • 条件循环
    • 将最有可能出现的条件放在最前面
    • 嵌套if减少判断(类似二分法)
    • 是永恒查找表(数组查找)的方法快过switch和if-else
    • 递归
  • 递归次数过多会导致栈溢出
  • 可以将递归的结果进行缓存,从而避免计算资源的浪费

字符串和正则表达式

str = 'one' +'two';//这样拼接字符串会产生一个中间变量
str = one;str += 'two';//这样拼接字符串则不会产生中间变量,有利于性能的提升
  • 正则表达式的核心,回溯法

快速响应的用户界面

  • 浏览器限制了js的执行时间,一种是限制大小,一种是限制执行时间。
  • 一般来说单个js的执行时间最好不要超过100ms,否则用户会认为等待的时间太久与页面失去联系
  • 定时器
    • 使用定时器会刷新所有的浏览器限制,这也是setTimeout,0的意义。
    • js定时器的延时通常不太精准,通常会相差几毫秒,所以定时器不能用于精准的定时。
    • 建议延迟最好有25ms,因为相对于大多数的UI来说,低于25ms渲染的时间不够用。
    • 在程序中应当限制高频率重复定时器的数量。
  • web workers(WW)
    • WW能使代码运行且不占用浏览器UI线程时间。能给web带来巨大的性能提升。
    • 因为WW不能访问UI线程,所有WW是JS的一个子集。
    • 与WW通信主要是通过PostMessage对象。
    • 使用importScripts来加载对应的js文件时是阻塞式的,直到所有的文件加载完成之后才会进行下面的动作。
    • 对于一些复杂的和UI线程无关的操作数据的方法可以考虑使用WW来提升效率。
  • web workers环境组成
    • navigator 包含四个属性: appName、appVersion、userAgent、platform。
    • location,与window.location相同,只不过所有的属性都是只读的。
    • self对象,指向全局worker对象
    • importScripts()方法,用来加载Worker所有用到的外部的JavaScript文件
    • 所有的ES对象,如:Object、Array等等
    • XMLHtppRequest构造器
    • setTimeout和setInterval方法
    • close()方法,它能立刻停止Worker的运行

因为WW有着完全不同的全局运行环境,所以无法在js中创建它,需要创建一个完全独立的js文件用来读取和执行对应的WW。而此文件一旦执行,就会创建一个新的线程和新的WW运行环境,该2文件会被异步下载,直达文件下载并执行完成之后才会启动此WW。

AJAX