js中的闭包的理解

在正常情况下,如果定义了一个函数,就会产生一个函数作用域,在函数体中的局部变量会在这个函数作用域中使用。一旦函数执行完成,函数所占空间就会被回收,存在于函数体中的局部变量同样会被回收,回收后将不能被访问到。那么如果我们期望在函数执行完成后,函数中的局部变量仍然可以被访问到,这能不能实现呢?答案是可以的,使用本节将要讲到的闭包,就可以实现这个目标。在学习闭包之前,我们需要掌握一个概念——执行上下文环境。

执行上下文环境

JavaScript每段代码的执行都会存在于一个执行上下文环境中,而任何一个执行上下文环境都会存在于整体的执行上下文环境中。根据栈先进后出的特点,全局环境产生的执行上下文环境会最先压入栈中,存在于栈底。当新的函数进行调用时,会产生的新的执行上下文环境,也会压入栈中。当函数调用完成后,这个上下文环境及其中的数据都会被销毁,并弹出栈,从而进入之前的执行上下文环境中。需要注意的是,处于活跃状态的执行上下文环境只能同时有一个,即图3-1所示的深色背景的部分。

我们再通过以下这段代码来看看执行上下文环境的变化过程。

1  var a = 10;    // 1.进入全局执行上下文环境
2  var fn = function (x) {
3      var c = 10;
4      console.log(c + x);
5  };
6  var bar = function (y) {
7      var b = 5;
8      fn(y + b);  // 3.进入fn()函数执行上下文环境
9  };
10 bar(20);  // 2.进入bar()函数执行上下文环境

从第1行代码开始,进入全局执行上下文环境,此时执行上下文环境中只存在全局执行上下文环境

     执行上下文环境
|                     |
|                     |
|                     |
|   全局执行上下文环境   |  <---- 活跃状态的上下文环境
 ____________________

当代码执行到第10行时,调用bar()函数,进入bar()函数执行上下文环境中

    执行上下文环境
|                       |
|                       |
|  bar()函数执行上下文环境 |   <---- 活跃状态的上下文环境
|    全局执行上下文环境    |
 ______________________

执行到第10行后,进入bar()函数中,执行到第8行时,调用fn()函数,进入fn()函数执行上下文环境中。

     执行上下文环境
|                       |
|   fn函数执行上下文环境   |   <---- 活跃状态的上下文环境
|   bar函数执行上下文环境  |
|    全局执行上下文环境    |
 ______________________

进入fn()函数中,执行完第5行代码后,fn()函数执行上下文环境将会被销毁,从而弹出栈。

     执行上下文环境
|                       |
|                       |
|   bar函数执行上下文环境  |   <---- 活跃状态的上下文环境
|    全局执行上下文环境    |
 ______________________

fn()函数执行上下文环境被销毁后,回到bar()函数执行上下文环境中,执行完第9行代码后,bar()函数执行上下文环境也将被销毁,从而弹出栈。

     执行上下文环境
|                     |
|                     |
|                     |
|   全局执行上下文环境   |  <---- 活跃状态的上下文环境
 ____________________

最后全局上下文环境执行完毕,栈被清空,流程执行结束。像上面这种代码执行完毕,执行上下文环境就会被销毁的场景,是一种比较理想的情况。有另外一种情况,虽然代码执行完毕,但执行上下文环境却被无法干净地销毁,这就是我们要讲到的闭包。

闭包的概念

对于闭包的概念,官方有一个通用的解释:一个拥有许多变量和绑定了这些变量执行上下文环境的表达式,通常是一个函数。闭包有两个很明显的特点。· 函数拥有的外部变量的引用,在函数返回时,该变量仍然处于活跃状态。· 闭包作为一个函数返回时,其执行上下文环境不会被销毁,仍处于执行上下文环境中。在JavaScript中存在一种内部函数,即函数声明和函数表达式可以位于另一个函数的函数体内,在内部函数中可以访问外部函数声明的变量,当这个内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

我们来看看下面这段代码。

1  function fn() {
2      var max = 10;
3      return function bar(x) 
4          if (x > max) {
5              console.log(x);
6          }
7      };
8  }
9  var f1 = fn();
10 f1(11);  // 11

代码开始执行后,生成全局上下文环境,并将其压入栈中。

    执行上下文环境
|                    |
|                    |
|                    |
|   全局执行上下文环境  |  <---- 活跃状态的上下文环境
 ____________________

代码执行到第9行时,进入fn()函数中,生成fn()函数执行上下文环境,并将其压入栈中

     执行上下文环境
|                       |
|                       |
|   fn函数执行上下文环境   |   <---- 活跃状态的上下文环境
|    全局执行上下文环境    |
 ______________________

fn()函数返回一个bar()函数,并将其赋给变量f1。当代码执行到第10行时,调用f1()函数,注意此时是一个关键的节点,因为f1()函数中包含了对max变量的引用,而max变量是存在于外部函数fn()中的,此时fn()函数执行上下文环境并不会被直接销毁,依然存在于执行上下文环境中。

      执行上下文环境
|                         |
|    bar函数执行上下文环境   |   <---- 活跃状态的上下文环境
|    fn函数执行上下文环境    |   
|    全局执行上下文环境      |
 ___________________________

等到第10行代码执行结束后,bar()函数执行完毕,bar()函数执行上下文环境才会被销毁,同时因为max变量引用会被释放,fn()函数执行上下文环境也一同被销毁。最后全局上下文环境执行完毕,栈被清空,流程执行结束。从分析就可以看出闭包所存在的最大的一个问题就是消耗内存,如果闭包使用越来越多,内存消耗将越来越大。

备注:摘自于书籍:《Javasript 重难点实例精讲》

posted @ 2025-06-12 11:08  heshanwan  阅读(9)  评论(0)    收藏  举报