让人恍然大悟的词法做用域及做用域链讲解

当JavaScript代码执行一段可执行代码(executable code)时,会建立对应的执行上下文(execution context)。javascript

对于每一个执行上下文,都有三个重要属性:html

  • 变量对象(Variable object,VO)
  • 做用域链(Scope chain)
  • this

前面已经讲解了this,今天来说讲做用域及做用域链。前端

做用域

做用域是指程序源代码中定义变量的区域。java

做用域规定了如何查找变量,也就是肯定当前执行代码对变量的访问权限。git

JavaScript 采用词法做用域(lexical scoping),也就是静态做用域。github

所谓的词法做用域,就是代码在编写过程就体现出来的做用范围。代码一旦写好,不用执行, 做用范围就已经肯定好了,这个就是所谓的词法做用域。函数

词法做用域的规则:post

  1. 函数容许访问函数外的数据 (也有就近原则)
  2. 整个代码结构中只有函数能够限定做用域
  3. 做用域内首先使用变量提高分析
  4. 若是当前做用域找到所需变量,则中止查找

*与词法做用域相对的是动态做用域,函数的做用域是在函数调用的时候才决定的。ui

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar(); // 1
复制代码

分析下执行过程:this

执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,若是没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,因此结果会打印 1。

假设JavaScript采用动态做用域,让咱们分析下执行过程:

执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。若是没有,就从调用函数的做用域,也就是 bar 函数内部查找 value 变量,因此结果会打印 2。

看一个《JavaScript权威指南》中的例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
复制代码
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
复制代码

两段代码都会打印:local scope。由于JavaScript采用的是词法做用域,函数的做用域基于函数建立的位置。

引用《JavaScript权威指南》的回答就是:

JavaScript 函数的执行用到了做用域链,这个做用域链是在函数定义的时候建立的。嵌套的函数 f() 定义在这个做用域链里,其中的变量 scope 必定是局部变量,无论什么时候何地执行函数 f(),这种绑定在执行 f() 时依然有效。

做用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,若是没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫作做用域链。

这里不具体介绍函数的[[scope]]属性,只介绍一下怎么简单的分析做用域链。

做用域链绘制规则以下:

  1. 全局变量,函数声明都是属于0级链,每一个对象占一个位置
  2. 遇到函数声明就延伸一个链出来,一级级展开
  3. 访问变量时首先从当前函数内部查找,若是当前做用域没有定义,往上级链中检查
  4. 如此往复,直到0级链,若是0级没有,则这个变量为undefined

以代码为例:

function func1(){
   alert(num);  
}

function func2(){
   var num=456;
   function func3(){
       func1();
   }
   func3();  
}

func2();//结果显示:num is not defined
复制代码

上述代码做用域链以下:

分析下代码执行流程:

  1. 当程序执行到func2时,进入func2的执行环境
  2. 当程序执行到func3时,在func3上下文中没有找到func1
  3. 回到1级做用域,即func3声明位置所在的1级做用域寻找,没有找到
  4. 回到func2声明时所在0级做用域寻找,找到后进入func1的上下文环境
  5. func1归属于0级做用域,内部只能访问func1内部做用域及0级做用域,在func1做用域内没有找到num,回到0级做用域,也没有找到,抛出错误

请注意:

  1. 只有函数声明会延伸下级链,调用只会查找,不会展开下级链
  2. 全部链都能访问到0级链,0级链只能访问0级链

相关系列: 从零开始的前端筑基之旅(超级精细,持续更新~)

若是你收获了新知识,就给做者点个赞吧~他急需支持~

参考文章:

  1. ****JavaScript深刻之词法做用域和动态做用域****
  2. ****JavaScript深刻之做用域链****
  3. ****JS-词法做用域 做用域链****
相关文章
相关标签/搜索