高性能JavaScript 总结

206 阅读7分钟

一. 页面加载阻塞

script标签放在head中会造成页面加载阻塞,所以需要延迟加载js脚本方式有

  1. defer:script标签属性,异步加载脚本,不会阻塞页面加载,并不是所有浏览器都支持
  2. js动态加载,使用DOM动态追加srcipt标签
  3. script标签放到body闭合标签之前
  4. 推荐的无阻塞模式,先添加动态初始化页面所需的代码,初始化页面后再加载剩余代码

二. 数据存取

计算机科学经典问题:通过改变数据的存储位置来获得最佳的读写性能

作用域链和标识符解析:(变量查询的过程)

所谓标识符就是程序执行遇到的变量,

作用域链,在当前作用域中,层层查找相同标识符,

总结:每次遇到变量都会经历一次标识符解析的过程,用来决定获取或储存数据,该过程搜索执行环境的作用域链,查找同名标识符。

标识符解析性能

标识符的解释是有代价的,越深的变量读取数据越慢(全局变量就处于作用域链的最末端,因为最远所以读取速度最慢)

解决方案:如果函数将调用一次以上的全局变量,请使用局部变量代替全局变量。这样将只会遍历一次作用域链。

function initUI(){ 
    var doc = document, bd = doc.body, links = doc.getElementsByTagName( "a") 
}

总结

在JavaScript中,数据存储的位置会对代码整体性能产生重大的影响。数据存储共有4种方式:字面量、变量、数组项、对象成员。它们有着各自的性能特点。

  1. 访问字面量和局部变量的速度最快,相反,访问数组元素和对象成员相对较慢。
  2. 由于局部变量存在于作用域链的起始位置,因此访问局部变量比访问跨作用域变量更快。变量在作用域链中的位置越深,访问所需时间就越长。由于全局变量总处在作用域链的最末端,因此访问速度也是最慢的。
  3. 避免使用 with 语句,因为它会改变执行环境作用域链。同样 try-catch 语句中的catch 子句也有同样的影响,因此也要小心使用。
  4. 嵌套的对象成员会明显影响性能,尽量少用。
  5. 属性或方法在原型链中的位置越深,访问它的速度也越慢。
  6. 通常来说,你可以通过把常用的对象成员、数组元素、跨域变量保存在局部变量中来改善JavaScript性能,因为局部变量访问速度更快。

三、DOM编程

DOM和JS是两个独立的模块,访问DOM的次数越多,代码的运行速度越慢。 因此,通用的经验法则是:减少访问DOM的次数,把运算尽量留在ECMAScript这一端处理。

innerHTML

HTML集合一直与文档(DOM)保持着连接,每次你需要最新的信息时,都会重复执行查询的过程,哪怕只是获取集合里的元素个数(即访问集合的length属性)也是如此。这正是低效之源。

重排与重绘

重排:计算元素的几何属性,重新构造渲染树;元素的变动影响页面布局,会触发重排

重绘:重新绘制受影响的部分到屏幕

重绘和重排操作都是代价昂贵的操作,它们会导致Web应用程序的UI反应迟钝。

重排何时发生

  1. 添加或删除可见的DOM元素。·元素位置改变。
  2. 元素尺寸改变(包括:外边距、内边距、边框厚度、宽度、高度等属性改变)。·内容改变,例如:文本改变或图片被另一个不同尺寸的图片替代。
  3. 页面渲染器初始化。
  4. 浏览器窗口尺寸改变。

避免重排

  1. 使用绝对位置定位脱离文档流。

  2. 减少布局信息API的调用次数,会直接刷新渲染队列,触发重排获取正确的值

    例:(offsetTop, offsetwidth,clientHeightgetComputedStyle()(currentStyle in IE))

    IE和:hover:IE中大量的hover会降低响应速度

总结

访问和操作DOM是现代Web应用的重要部分。但每次穿越连接ECMAScript和DOM两个岛屿之间的桥梁,都会被收取“过桥费”。为了减少DOM编程带来的性能损失,请记住以下几点:

  1. 最小化 DOM 访问次数,尽可能在JavaScript端处理。
  2. 如果需要多次访问某个DOM节点,请使用局部变量存储它的引用。
  3. 小心处理HTML集合,因为它实时连系着底层文档。把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中。
  4. 如果可能的话,使用速度更快的APl,比如 querySelectorAll()和firstElementChild。要留意重绘和重排;批量修改样式时,“离线”操作 DOM树,使用缓存,并减少访问布局信息的次数。

image.png 0

  1. 动画中使用绝对定位,使用拖放代理。·使用事件委托来减少事件处理器的数量。

四、算法和流程控制

循环-性能优化-两个方向

  1. 减少每次迭代处理的事务

  2. 减少迭代次数

减少迭代次数———达夫设备

var a = [1,2,3,4,5,6,7,8,9,10]
var it = Math.floor(a.length / 4),st = a.length % 4,i = 0
do{
  switch(st) {
    case 0: console.log(0,a[i++]);
    case 7: console.log(7,a[i++]);
    case 6: console.log(6,a[i++]);
    case 5: console.log(5,a[i++]);
    case 4: console.log(4,a[i++]);
    case 3: console.log(3,a[i++]);
    case 2: console.log(2,a[i++]);
    case 1: console.log(1,a[i++]);
  }
  st = 0
}while(--it)

循环速度排行

(do-while, while) > for > forEach

forEach为函数调用(慢在函数查找

达夫设备 速度和 (do-while, while) 相差无几,有时更慢,但是比其他循环更快

总结

JavaScript和其他编程语言一样,代码的写法和算法会影响运行时间。与其他语言不同的是,JavaScript可用资源有限,因此优化技术更为重要。

注:由于JavaScript 是解释性语言,与编译性语言不同的是,它无须编译,而是将代码以字符串的形式交给JavaScript 引擎来执行。因此,代码性能在一定程度上取决于客户端浏览器的JavaScript 引擎。

  • for、while和 do-while循环性能特性相当,并没有一种循环类型明显快于或慢于其他类型。
  • 避免使用for-in循环,除非你需要遍历一个属性数量未知的对象。
  • 改善循环性能的最佳方式是减少每次迭代的运算量和减少循环迭代次数。·通常来说,switch总是比if-else快,但并不总是最佳解决方案。
  • 在判断条件较多时,使用查找表比if-else和 switch更快。
  • 浏览器的调用栈大小限制了递归算法在JavaScript中的应用;栈溢出错误会导致其他代码中断运行。
  • 如果你遇到栈溢出错误,可将方法改为迭代算法,或使用Memoization来避免重复计算。

运行的代码数量越大,使用这些策略所带来的性能提升也就越明显。

编程实践

使用 Object/Array 直接量

//创建一个对象 较慢
var myObject = new 0bject();
my0bject.name = "Nicholas";
my0bject.count =50;
//较快   直接量
var myObject ={
  name: "Nicholas"
  count: 50
}

JavaScript提出了一些独一无二的性能挑战,这与你组织代码的方式有关。随着Web应用变得越来越高级,包含的 JavaScript 代码也越来越多,各种模式和反模式'也逐渐出现。为了编写更高效的代码,请牢记以下编程实践:

  1. 通过避免使用eval()和Function()构造器来避免双重求值带来的性能消耗。同样的,给setTimeout()和setInterval()传递函数而不是字符串作为参数。
  2. 尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快。·避免做重复的工作。当需要检测浏览器时,可使用延迟加载或条件预加载。
  3. 在进行数学计算时,考虑使用直接操作数字的二进制形式的位运算。
  4. JavaScript的原生方法总会比你写的任何代码都要快。尽量使用原生方法。