深入浅出javascript-4-作用域链

10次阅读

共计 1988 个字符,预计需要花费 5 分钟才能阅读完成。

通过前面三篇文章我们了解了执行上下文,作用域以及变量提升。并且在上一篇文章中我们留了一个小悬念,就是关于变量查找的完整过程。那么今天我们就接着介绍变量查找的过程,这就涉及到一个很重要的概念 —— 作用域链

首先我们看下面这段代码

function bar() {console.log(myName) 
} 
function foo() { 
    var myName = "wens" 
    bar()} 
var myName = "leon" 
foo()

在 foo 函数和全局环境中都定义了同名变量 myName,那么执行 foo 函数之后应该打印哪个 myName 的值呢,看过前面几篇文章的同学一定已经知道如何通过执行上下文的进栈和出栈分析这段代码的执行过程,当执行到第 6 行的时候,这时候的调用栈是下面这样:

如果我们觉得 myName 变量会按照从上到下的查找方式查找的话,那么打印的就是 ‘wens’,可实际情况打印出来的是 ‘leon’。下面我们就来讲解下为什么?

作用域链

关于作用域链,很多同学会对这个概念有错误的理解。但如果我们理解了调用栈、执行上下文、词法环境、变量环境等概念,那么理解作用域链就会变得很容易。所以再次建议同学把前面几篇文章好好读一读。

在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。

当一段代码使用了一个变量时,JavaScript 引擎首先会在“当前的执行上下文”中查找该变量,比如上面那段代码在查找 myName 变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找。具体我们看下面这张图:

从图中可以看出,bar 函数和 foo 函数的 outer 都是指向全局上下文的,也就是说如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。那么这个查找的链条就称为作用域链。

现在知道作用域链是啥子玩意,那接下来就有另一个问题了,foo 函数调用的 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?

接下来就是我们的主角 —— 词法作用域亮相了。在 JavaScript 执行过程中,作用域链是由词法作用域决定的。

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,而不是执行过程。所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

我们通过下面的代码来更清楚的理解:

var name = 'wens';
function f1() {
    var name = 'leon';
    function f2() {
        var name = 'tracy';
        function f3() {var name = 'henry';}
    }
}

f1();

当函数 f1 执行的时候,根据上面对作用域链的定义,这时候查找 name 变量的顺序是这样的:f3 函数作用域 -> f2 函数作用域 -> f1 函数作用域 -> 全局作用域。

那么现在回过头来看开篇那个例子就知道为什么打印的是 ‘leon’ 了:根据作用域链的定义可知,由于创建 bar 函数的“位置”是在全局,所以查找 myName 变量的顺序是 bar 函数作用域 -> 全局作用域,而在 bar 函数内部没有 myName 变量,进而在全局作用域找到。

块级作用域中的变量查找

前面我们通过全局作用域和函数级作用域来分析了作用域链,那接下来我们再来看看块级作用域中变量是如何查找的?顺便来完善下变量查找的完整过程

我们还是先看下面这段有点复杂的代码:

function bar() { 
    var myName = "tracy" 
    let test1 = 4 
    if (1) { 
        let myName = "henry" 
        console.log(test) 
    } 
} 
function foo() { 
    var myName = "wens" 
    let test = 2 
    { 
        let test = 3 
        bar()} 
} 
var myName = "leon" 
let myAge = 20 
let test = 1 栈顶 
foo()

在上篇文章中我们已经介绍过了,ES6 是支持块级作用域的,当执行到代码块时,如果代码块中有 let 或者 const 声明的变量,那么变量就会存放到该函数的词法环境中。并且对于执行上下文中的变量查找过程是由词法环境的栈顶向栈底,如果没有找到就去变量环境中查找。
本文基于词法作用域我们又知道了作用域链的查找规则,对于上面这段代码,当执行到 bar 函数内部的 if 语句块时,其调用栈的情况如下图所示:

那么打印的 test 值就应该是 1。

没有想对答案的同学好好看看上一篇文章。

总结

好了,今天的内容就讲到这里,下面我们来回顾下今天的内容:

  • 首先,介绍了什么是作用域链,我们把通过作用域查找变量的链条称为作用域链;作用域链是通过词法作用域来确定的。
  • 然后,介绍了在块级作用域中是如何通过作用域链来查找变量的(变量查找的完整路线)。

希望同学结合上一篇文章一起消化。下一篇文章我们介绍闭包。

正文完
 0