高性能JavaScript——2、数据存取

管理作用域

作用域概念是理解JavaScript的关键所在, 不仅仅从性能角度, 还包括从功能的角度。 作用域对JavaScript有许多影响,从确定哪些变量可以被函数访问,到确定出this的赋值。JavaScript作用域同样关系到性能, 要理解速度和作用域的关系, 首先要正确地理解作用域的工作原理。

作用域链和标识符解析

内部属性[[[Scope]] 包含了 一个函数被创建的作用域中对象的集合。 这个集合被称为函数的 作用域链, 它决定哪些数据能被函数访问。

在函数执行过程中, 每遇到一个变量, 都会经历一次标识符解析过程以决定从哪里获取或存储数据。 该过程搜索执行环境的作用域链, 查找同名的标识符。 搜索过程从作用域链头部开始, 也就是当前运行函数的活动对象。 如果找到, 就使用这个标示符对应的变量; 如 果没找到, 继续搜索作用域链中的下一个对象。 搜索过程会持续进行, 直到找到标识符,若无法搜索到匹配的对象, 那么标识符将被视为是未定义的。 在函数执行过程中, 每个标 识符都要经历这样的搜索过程。 在前面的代码示例中, 函数访问sum、 numl 、 num2时都会产生搜索过程, 正是这个搜索过程影响了性能。

标识符解析的性能

  • 在执行环境的 作用域链中, 一个标识特所在的位置越深, 它的读写速度也就越慢。
  • 函数中读写局 部变量总是最快的, 而读写全局变量通常是最慢的(优化JavaScript引擎在某些情况下能有所改善)。采用优化过的JavaScript引擎的浏览器,比如Chrome和Safari 4,访问跨作用域的标识符时就没有类似的性能损失。
  • 通过以下方法减少对性能的影响:先将全局变量的引用存储在一个局部变量中,然后使用这个局部变量代替全局变量

改变作用域链

  • with
    • 当代码执行到with悟句时,执行环境的作用域链临时被改变了。一个新的变量对象被创建,它包含了参数指定的对象的所有属性。这个对象被推入作用域链的首位,这意味着函数的所有局部变量现在处干第二个作用域链对象中,因此访问的代价更高了

    • 通过把document对象传递给withi吾句,一个包含了document对象所有属性的新的可变变量被置于作用域链的头部。这使得访问document对象的属性非常快,而访问局部变量则变慢了,。因此,最好避免使用with语句

  • try-catch
    • 当try代码块中发生错误,执行过程会自动跳转到catch子句,然后把异常对象推入一个变量并置于作用域的首位。在catch代码块内部,函数所有局部变量将会放在第二个作用域链对象中。
    • 如果使用得当, try-catch是个非常有用的居句, 因此不建议完全弃用。 如果你准备使用 try-catch,请确保了解可能会出现的错误。try-catch语句不应该被用来解决JavaScript错误。 如果你知道某个错误经常出现, 那说明是代码本身有问题, 应该尽早被修复。
    • 你尽量简化代码来使得catch子句对性能的影响最小化。一种推荐的做陆是将错误委托给一个函数来处理, 比如下面的例子:
      try{
      }catch(er){
      	handleError(er)
      }
      
      函数handleError()是catch 子句中唯一执行的代码。 该函数接受错误产生的异常对象为参数, 你可以用适当的方式灵活地处理错误。 由于只执行一条语句, 且没有局部变量的访问, 作用域链的临时改变就不会影响代码性能。

动态作用域

  • 无论是withi吾句还是try-catch语句的catch子句,或是包含eval()的函数,都被认为是动态作用域。动态作用域只存在于代码执行过程中,因此无法通过静态分析(查看代码结构)检测出来。

  • 只有在确实有必要时才推荐使用动态作用域。

闭包、 作用域和内存

  • 闭包是JavaScript最强大的特性之一。允许函数访问局部作用域之外的数据。
  • 频繁访问跨作用域的标识符时, 每次访问都会带来性能损失
  • 由于闭包的[[Scope]]属性包含了与执行环境作用域链相同的对象的引用,因此会产生副作 用。通常来说,函数的活动对象会随着执行环境一同销毁。但引入闭包时.由于引用仍然存在于闭包的[[Scope]]属性中, 因此激活对象无法被销毁
  • 它同时关系到内存和执行速度。
  • 可以遵照本章节中早先对跨作用域变量的处理建议, 来减轻闭包对执行速度的影响:将常用的跨作用域变量存储在局部变量中, 然后直接访问局部变量。

对象成员

  • 对象成员包括属性和方法。
  • 访问对象成员的速度比访问字面量或变量要慢, 在某些浏览器中比访问数组元素还要慢。 为了理解其中的原因, 有必要先理解JavaScript中对象的本质。

原型

  • JavaScript中的对象是基于原型的。 原型是其他对象的基础, 它定义并实现了一个新创建的对象所必须包含的成员列表。 这一概念完全不同于传统面向对象编程语言中的 “类” 的慨念, “类” 定义了创建新对象的过程, 而原型对象为所有对象实例所共享, 因此这些实例也共享了原型对象的成员。
  • 对象可以有两种成员类型: 实例成员(也称为own成员)和原型成员

原型链

  • 对象在原型链中存在的位置越深,找到它也就越慢。
  • 使用优化过的JavaScript引擎的新型浏览器在此过程表现优异

嵌套成员

  • 对象成员嵌套得越深,读取速度就会越慢。
  • 执行location.href总是比window.location.href要快

缓存对象成员值

  • currentClassName = element.className
  • 函数中如果要多次读取同 个对象属性,最佳做法是将属性值保存到局部变量中,特别是在处理嵌套对象成员时。
  • 这种优化技术并不推荐用于对象的成员方法。因为许多对象 方法使用this判断执行环境,把一个对象方法保存在局部变量会导致this绑定到window,而this值的改变会使得JavaScript引擎无法正确解析它的对象成员,进而导致程序出错。

小结

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

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

通过以上策略,你可以显著提升那些需要使用大量JavaScript的Web应用的实际性能。

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值