执行上下文、作用域链、闭包

118 阅读5分钟

这两个函数的行为不同,主要是因为 JavaScript 中的变量声明提升(hoisting)和作用域规则。让我们详细分析一下这两种情况。

情况 1:带有 var 声明的局部变量

var x = 10;
function f() {
    console.log(x);
    var x = 20;
}
f();

解释:

  1. 变量声明提升:在 JavaScript 中,使用 var 声明的变量会被提升到其所在作用域的顶部,但初始化不会被提升。
  2. 作用域:在函数 f 内部,var x 声明了一个局部变量 x,它遮蔽了外部的全局变量 x
  3. 执行顺序
    • 当进入函数 f 时,局部变量 x 被声明并初始化为 undefined(因为变量声明提升)。
    • 执行 console.log(x) 时,由于局部变量 x 已经声明但尚未赋值,所以输出 undefined
    • 然后执行 x = 20,给局部变量 x 赋值为 20

因此,这段代码的输出是 undefined

情况 2:没有 var 声明的局部变量

var x = 10;
function f() {
    console.log(x);
    x = 20;
}
f();

解释:

  1. var 声明:在函数 f 内部,x = 20 没有使用 var 关键字声明,这意味着它会尝试修改当前作用域链中的变量 x。如果没有找到局部变量 x,则会修改全局变量 x
  2. 执行顺序
    • 进入函数 f 时,没有新的局部变量 x 被声明。
    • 执行 console.log(x) 时,由于没有局部变量 x,JavaScript 会在当前作用域链中查找,找到全局变量 x,其值为 10,所以输出 10
    • 然后执行 x = 20,这会修改全局变量 x 的值为 20

因此,这段代码的输出是 10

总结对比

  • 情况 1:由于 var x 在函数内部声明,局部变量 x 遮蔽了全局变量 x,并且由于变量声明提升,console.log(x) 输出的是 undefined

    var x = 10;
    function f() {
        console.log(x); // undefined
        var x = 20;     // 局部变量 x 被声明并赋值为 20
    }
    f();
    
  • 情况 2:由于没有使用 var 声明局部变量 xconsole.log(x) 访问的是全局变量 x,输出 10,然后将全局变量 x 修改为 20

    var x = 10;
    function f() {
        console.log(x); // 10
        x = 20;         // 修改全局变量 x 的值为 20
    }
    f();
    

主要学习于王福朋博客:www.cnblogs.com/wangfupeng1…

一:执行上下文、作用域

  1. 这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”:
  • 变量、函数表达式——变量声明,默认赋值为undefined;
  • this——赋值;
  • 函数声明——赋值;

****并且准备资源的顺序是有先后的,先预编译 函数 > 变量

image.png

等价于

image.png

所以,图中汇报错: a is not a function

执行上下文环境:通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域(如下图)

image.png

函数每被调用一次,都会产生一个新的执行上下文环境。(this的取值是执行上下文环境的一部分)

  1. this
  1. 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程——执行上下文栈。如下图:

image.png

  1. javascript除了全局作用域之外,只有函数可以创建作用域。

作用域在函数定义时就已经确定了。而不是在函数调用时确定

  1. 在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。

自由变量取值:要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记。

image.png

上面描述的只是跨一步作用域去寻找。

如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

这个一步一步“跨”的路线,我们称之为——作用域链。

闭包

定义:闭包是指有权访问另外一个函数作用域中的变量的函数。

(闭包是指那些能够访问自由变量的函数,这里的自由变量是指外部作用域中的变量)

你只需要知道应用的两种情况即可——

1.函数作为返回值

2.函数作为参数传递

第一,函数作为返回值

image.png

bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。

第二,函数作为参数被传递

image.png

fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100(因为max是自由变量)。

函数调用完成之后,其执行上下文环境不会接着被销毁。这就是需要理解闭包的核心内容。使用闭包会增加内容开销,现在很明显了吧!

应用:节流,防抖

优点:私有化数据,在私有化数据的基础上保持数据

缺点:可能会导致内存泄漏,内部的变量不会被自动回收掉

  1. g