理解JavaScript中的做用域和做用域链

做用域

做用域就是变量与函数的可访问范围,即做用域控制着变量与函数的可见性和生命周期。
在JavaScript中,变量的做用域有全局做用域和局部做用域两种。前端

做用域链

函数对象有一个内部属性[[Scope]],包含了函数被建立后的做用域中对象的集合,
这个集合被称为函数的做用域链,它决定了哪些数据能被函数访问。
示例:
当一个函数建立后,它的做用域链会被建立此函数的做用域中可访问的数据对象填充。java

function add(num1, num2){
  var sum = num1 + num2;
  return sum;
}

在函数add建立时,它的做用域链中会填入一个全局对象,该全局对象包含了全部全局变量,以下图所示(注意:图片只列举了所有变量中的一部分):
js做用域链_01
执行add函数时会建立一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。
每一个运行期上下文都有本身的做用域链,用于标识符解析。
当运行期上下文被建立时,而它的做用域链初始化为当前运行函数的[[Scope]]所包含的对象,这些值按照它们出如今函数中的顺序被复制到运行期上下文的做用域链中。
它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的全部局部变量、命名参数、参数集合以及this,而后此对象会被推入做用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的做用域链以下图所示:
js做用域链_02
在函数执行过程当中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从做用域链头部,也就是从活动对象开始搜索,查找同名的标识符,若是找到了就使用这个标识符对应的变量,若是没找到继续搜索做用域链中的下一个对象,若是搜索完全部对象都未找到,则认为该标识符未定义。函数执行过程当中,每一个标识符都要经历这样的搜索过程。c++

做用域链和代码优化

从做用域链的结构能够看出,在运行期上下文的做用域链中,标识符所在的位置越深,读写速度就会越慢。
如上图所示,由于全局变量老是存在于运行期上下文做用域链的最末端,所以在标识符解析的时候,查找全局变量是最慢的。因此,在编写代码的时候应尽可能少使用全局变量,尽量使用局部变量。一个好的经验法则是:若是一个跨做用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。缓存

改变做用域链

函数每次执行时对应的运行期上下文都是独一无二的,因此屡次调用同一个函数就会致使建立多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每个运行期上下文都和一个做用域链关联。通常状况下,在运行期上下文运行的过程当中,其做用域链只会被 with 语句和 catch 语句影响。
with语句是对象的快捷应用方式,用来避免书写重复代码。
对with语句来讲,会将指定的对象添加到做用域链中,对catch语句来讲,会建立一个新的变量对象,其中包含的是被抛出的错误对象的声明。
此时,做用域链中函数的全部局部变量所在的做用域对象会被推后,访问代价变高了。
在实际应用中,应少用with,把catch中的错误委托给一个函数处理。函数

没有块级做用域

if(true){
    var i = 0;
    i++;
  }
  console.log(i); //1

若是在c、c++或java语言中,if语句执行完毕后i会被销毁,而在js中,if语句中的变量声明是添加到了当前函数的执行环境中,因此在if语句以后仍然能够正常访问。优化

模仿块级做用域

(function(){
  //这里是块级做用域
})();

将函数声明包含在一对圆括号中,表示它其实是一个函数表达式,而紧随其后的另外一对圆括号会当即调用这个函数。this

小结

  • 做用域就是变量和函数的可访问范围,一般,局部环境中的变量和函数是不能被外部环境访问的;spa

  • 做用域链决定了哪些数据可以被当前函数访问以及访问的顺序;code

  • 函数建立时,会建立一个Global Object,填入它的做用域链;函数执行时,会建立一个运行期上下文的对象,它定义了函数执行时的环境。函数执行环境包含一个活动对象,该对象包含了函数的全部局部变量、命名参数、参数集合以及this,它会被推入做用域链的最前端;对象

  • 函数执行过程,每遇到一个变量,都会经历一次标识符解析的过程(逐级向上搜索做用域链)以决定从哪里获取和存储数据;

  • 全局变量存在于运行期上下文做用域链的最末端,查找最慢,因此咱们应该尽量少使用全局变量,若是使用,就先用局部变量缓存下来;

  • 在运行期上下文运行的过程当中,其做用域链只会被 with 语句和 catch 语句影响,应少用with,把catch中的错误委托给一个函数处理;

  • js中没有块级做用域,可是咱们能够模仿实现它。

相关文章
相关标签/搜索