一、引例

  • 需求:三个按钮,每个按钮点击时,弹出其对应序号

    1. <button>第一个按钮</button>
    2. <button>第二个按钮</button>
    3. <button>第三个按钮</button>
  • 错误的写法

    var bts = document.querySelectorAll('button');
    for(var i = 0; i < bts.length; i++) {
      let bt = bts[i];
      bt.onclick = function() {
          console.log('这是第 ' + (i + 1) + '个按钮'); 
      }
    }
    // 无论点哪个按钮,都会输出为第四个按钮
    

    由于 var i为全局变量,因此当 onclick所绑定的函数执行时,i 早已变为了3

  • ES6的写法

    for(let i = 0, length = bts.length; i < length; i++) {
      // 其余代码不变
    }
    

    let i为局部变量,每个 onclick绑定函数的 i 都是独立的,互不影响;
    另外,bts.length每次执行 for语句时都会重新计算,因为 bts是伪数组,因此,为了减少计算量,将其放在 let i = 0, length = bts.length

  • 闭包的写法

    for(let i = 0, length = bts.length; i < length; i++) {
      (function (j) {
          let bt = bts[j];
          bt.onclick = function() {
              console.log(i + 1);
          }
      })(i);
    }
    

二、闭包的概念

  • **内部函数**使用了 **外部函数**的值,就产生了闭包(外部函数需要被调用
  • 闭包就是 嵌套的内部函数,即下面的 w_in() ```javascript function w_out() { let a = 1; let b = 2; function w_in() { // w_in 就是闭包 console.log(a); } w_in(); }

w_out();


闭包中,由于只引用了外部的 **a**,因此 **b** 并不包含在闭包中,如下图<br />![1A236D19-75C2-4201-853B-2346BF5974E4_4_5005_c.jpeg](https://cdn.nlark.com/yuque/0/2021/jpeg/22695934/1631707441103-abbc5457-f0d9-4540-ac2c-2538bf259346.jpeg#clientId=uc7573e84-1063-4&from=ui&id=u578f83c6&margin=%5Bobject%20Object%5D&name=1A236D19-75C2-4201-853B-2346BF5974E4_4_5005_c.jpeg&originHeight=333&originWidth=1226&originalType=binary&ratio=1&size=88092&status=done&style=none&taskId=u461be2f0-86b7-4354-93a1-0edb10340a1)

闭包在代码**编译时**就产生了,并不需要调用
```javascript
function w_out() {
  // 第 2 行时,闭包就已经产生(由于函数提升)
  let a = 1;
  function w_in() {
    a++;
  }
  return w_in;
}

当闭包成为垃圾对象时,闭包死亡

function w_out() {
  let a = 1;
  function w_in() {
    a++;
  }
  return w_in;
}
let w = w_out();
w = null;        // 此时,闭包死亡

三、常用的闭包(2种)

1、将函数作为另一个函数的返回值

由于函数执行上下文会在函数调用完毕后,自动删除,为了防止其某些内部变量消失,可采用闭包的做法
非闭包内的局部变量,执行完毕后会被删除

function w_out() {
  let a = 1;
  function w_in() {
    a++;
    console.log(a);
  }
  return w_in;
}

let w = w_out();
w();    // 2
w();    // 3


2、将函数作为实参给另一个函数(setInterval)

function showDelay(msg) {
  setInterval(function() {
    console.log(msg);
  }, 2000);
}
showDelay("this is message !!!");

注意:第一个参数 function() {...}才是闭包,setIntetval自身不是闭包


四、闭包的作用

1、延长局部变量的生命周期

非闭包内的变量都会被删除,包括闭包本身的函数名

function w_out() {
  let a = 1;
  let b = 1;
  function w_in() {
    a++;
    console.log(a);
  }
  return w_in;
}

let w = w_out();
w();    // 2
w();    // 3

上述代码中,bw_in 这个**变量名**都会被释放,但w_in所对应的函数对象不会被释放,而是指向了 w

2、使得外部能够访问内部变量


五、模块化(基于闭包)

1、普通方法

  • moudle.js ```javascript function test() { let way1 = () => {
      console.log('way1()');
    
    } let way2 = () => {
      console.log('way2()');
    
    } return {
      way1: way1,
      way2: way2
    
    } }

// vue 中可使用 export function test() {…} 替代


- **index.html**
```javascript
<script src="./moudle.js"></script>
<script>

    let w = test();
    w.way1();
    w.way2();

</script>

2、匿名函数自调用方法

  • moudle.js ```javascript (function() { let way1 = () => {
      console.log('way1()');
    
    } let way2 = () => {
      console.log('way2()');
    
    } window.w = { // 在 window 中绑定,等于 var w = {…}
      way1: way1,
      way2: way2
    
    } })()

- **index.html**
```javascript
<script src="./moudle.js"></script>
<script>
    w.way1();
    w.way2();
</script>

显然,匿名函数自调用更方便,不用额外执行一次原函数


六、闭包的缺点

容易导致内存溢出,因此需要确保完成闭包后,及时将闭包删除