美文网首页
作用域(scope)和执行环境(context)

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

作者: yyyzhen | 来源:发表于2017-09-30 16:57 被阅读0次

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

    作用域

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

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

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


    作用域图示(截取自《你不知道的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执行环境

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

    上例图解

    执行环境和作用域的区别

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

    相关文章

      网友评论

          本文标题:作用域(scope)和执行环境(context)

          本文链接:https://www.haomeiwen.com/subject/zyxkjxtx.html