作用域(scope)和执行环境(context)

JavaScript的scope和context都是不能被我们直接使用的东西,存在于JavaScript的整个执行过程,分为代码编译阶段和代码执行阶段,在代码编译阶段,编译器将代码翻译成可执行代码,此时会确定函数的作用域和作用域链;在代码的执行阶段,引擎执行代码时会创建代码的执行环境。所以首先我们要明白一点,执行环境和作用域不是同一个东西

作用域

作用域是负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前的执行的代码对这些标识符的访问权限。——《你不知道的JavaScript(上卷)》

JavaScript中作用域可以分为全局作用域和局部作用域。
全局作用域是整个程序在运行时都能访问的;局部作用域中包含了函数作用域和块级作用域。

下图展示了函数作用域和全局作用域之间的关系:


作用域(scope)和执行环境(context)_第1张图片
作用域图示(截取自《你不知道的JavaScript(上卷)》)

块级作用域在ES3中有with和catch两个,在ES6中引入了let和const

    /* with */
    var obj = {a: 0}
    with(obj) {
      a = 1
    }
    console.log(obj)  // {a:1}
    console.log(a)  // ReferenceError: a is not define

    /* catch */
    try {
      undefined() // 制造一个错误
    } catch(err) {
      console.log(err) // 输出这个err
    }
    console.log(err) // ReferenceError: err is not define

利用catch的块作用域,我们可以在不兼容ES6的环境下使用let的兼容写法,可以看下面这个例子:

/* 无块级作用域 */
var i = 0;
for(i; i < 10; i++) {
  var j = i
  setTimeout(function(){
    console.log(j)  // 输出10个10
  })
}
/* 使用let */
var i = 0
for(i; i < 10; i++) {
  let j = i
  setTimeout(function(){
    console.log(j)  // 输出从0到9 10个数字
  })
}

/* 使用catch */
var i = 0;
for (i; i < 10; i++) {
  try {
    throw undefined  // 产生错误,值为undefined
  } catch (j) {
    j = i
    setTimeout(function () {
      console.log(j)  // 输出从0到9 10个数字
    })
  }
}

词法作用域
JavaScript是用的词法作用域,所以作用域和作用域链是在代码编译阶段就确定的东西,下面看一个简单的例子:

var a = 2;
function fn1() {
  console.log(a);
}
function fn2() {
  var a = 3;
  fn1();
}
fn2();  // 2

在上面的代码中,变量a、fn1和fn2是在全局作用域中声明的,fn1和fn2也产生了各自的函数作用域,但因为作用域和作用域链是在函数定义阶段就确定的,所以在执行阶段,fn1()输出的a就是全局变量中的a。

执行环境

每个函数都有自己的执行环境(execution context)。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。——《JavaScript高级程序设计》

console.log('global');
function func1() {
  console.log('func1');
  var func2 = function() {
    console.log('func2');
  }
  func2();
}
func1();
  1. 浏览器开始运行,环境栈中添加global执行环境,输出global
  2. 执行了func1(),环境栈中添加func1执行环境,输出func1
  3. 在func1中执行了func2(),环境栈中添加func2执行环境,输出func2
  4. func2执行完毕,环境栈中推出func2执行环境
  5. func1执行完毕,环境栈中推出func1执行环境

上面这个例子的环境栈变化如下图所示:

作用域(scope)和执行环境(context)_第2张图片
上例图解

执行环境和作用域的区别

自我理解(未确认):JavaScript的代码在执行前会确定作用域,产生作用域链,然后当执行到某个函数的时候,函数执行环境就被推入环境栈,执行环境上就会添加变量对象、活动对象、作用域链这些东西。变量对象和活动对象是差不多的,只是状态不一样,一个是执行前确定的,一个是执行时用到的,上面保存了函数中定义的变量或者函数声明。作用域是一套规则,用来确定在何处以及如何查找对象,当作用域嵌套的时候,就产生了作用域链,当前作用域没有找到变量的时候,就在上一层作用域查找,直到找到变量或者到达最外层作用域。

你可能感兴趣的:(作用域(scope)和执行环境(context))