JS基础:闭包

36 阅读3分钟

一句话说清楚

闭包就是js函数内的函数对象携带函数内的变量逃出函数的垃圾回收

闭包的基本概念

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

简单理解: 闭包就是能够读取其他函数内部变量的函数。在JavaScript中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

闭包的形成条件

  1. 嵌套函数: 在函数内部定义另一个函数
  2. 访问外部变量: 内部函数访问外部函数的变量
  3. 函数返回或传递: 内部函数被返回或以某种方式被外部访问
// 条件1: 嵌套函数
function outer() {
  const name = "Alice";
  
  // 条件2: 访问外部变量
  function inner() {
    console.log(name);
  }
  
  // 条件3: 函数返回
  return inner;
}

const myClosure = outer();
myClosure(); // "Alice" - 闭包形成

在这段代码中,inner访问了外部函数outter的变量name,那么即使outer函数执行完毕之后,name也不会被回收,就好像被inner函数“打包带走了一样”,但接下来要明确的一点是,即使inner函数被执行多次,counter依旧会维持自己的值

闭包的原理

首先理清楚三个概念,作用域作用域链执行上下文

作用域

对于一个js脚本来说作用域分为全局作用域函数作用域块级作用域。顾名思义就能明白全局作用域就是整个js脚本的范围,而函数作用域就是整个函数的范围,块级作用域就是大括号的那一部分范围,简单看以下代码就能明白

//全局作用域开始
let globalA = 1;
let globalB = 2;
let globalC = 3;
function testFunc(){
    //函数作用域开始
    let a = 0;
    let b = 0;
    let arr = [1,2,3,4];
    for(let val of arr){
        //块级作用域开始
        console.log(val);
        //块级作用域结束
    }
    //函数作用域结束
}
let globalD = 4;
//全局作用域结束

作用域链

那么好,通过以上的代码能够分清楚各个代码所表示的范围,那么落在每个范围内的变量(JS中函数也可以看作变量的一种)也就是每个作用域容纳的变量。当一个内部的作用域(比如说上面代码的块级作用域)访问一个变量的时候,他会先看自己的这部分作用域有没有这个变量名,如果访问的是val,那就有,那就直接用了,不继续向外层访问。
如果没有,那就继续向外层的函数作用域去访问访问,函数作用域还没有,那就再向外层,在这里也就是全局作用域去访问。那么这样的一个关系链就是被叫做作用域链

执行上下文

执行上下文那就是通过作用域链把这个函数内执行需要的所用变量压入栈中,然后这个函数结束之后,栈就会销毁这一部分,所以执行上下文是动态的

闭包执行原理

那么说完这三个重要概念之后,我们可以构想一种场景,一个函数返回一个函数对象,然后这个被返回的函数里面引用了原先外层函数的变量,那么当外层函数的执行上下文被销毁的时候,会为被返回函数对象所引用的外层函数的变量保留下来,这样一来被返回的函数它的作用域链才能够完整。其实闭包就可以简单理解为下面两个代码块之间的转换

let a = 0;
function inner(){
    console.log(a)
}
function outer(){
    let a = 0;
    const inner = function(){
        console.log(a);
    }
    return inner;
}