JavaScript 闭包与循环索引

456 阅读2分钟

闭包与循环索引同步

闭包 (新奇的解释)

  1. 什么是闭包(closure)?
    • 闭包是一种特殊的函数。
  2. 如何生成一个闭包?
    • 当一个内部函数引用了外部函数的数据(变量/函数)时, 那么内部的函数就是闭包
    • 所以只要满足**"是函数嵌套"、"内部函数引用外部函数数据"**
  3. 闭包特点:
    • 只要闭包还在使用外部函数的数据, 那么外部的数据就一直不会被释放
    • 也就是说可以延长外部函数数据的生命周期
  4. 闭包注意点:
    • 当后续不需要使用闭包时候, 一定要手动将闭包设置为null, 否则会出现内存泄漏
// 常规函数
function test() {
    var i = 666; // 局部变量
} // 只要代码执行到了大括号结束, i这个变量就会自动释放
console.log(i); // i is not defined
function test() {
    var i = 666;
    // 由于demo函数满足闭包的两个条件, 所以demo函数就是闭包
    return function demo() {
        console.log(i);
    }
}
let fn = test();
fn(); // 666

循环索引同步

  • 要拎清: 函数定义和函数执行

最基本的认识

  • JavaScript 是单线程的

var

  • 默认情况下通过 var 定义的变量, 只要不是定义在函数中都是==全局变量==
for 循环用 var, for 完调函数

for 完了才执行到 test, 此时全局变量 i 已经是 3 了, 我们称这种情况为循环索引==不同步==

for(var i = 0; i < 3; i++){ // 0 1 2 3
    function test() {
        console.log(i); // 3
    }
}
test();
for 循环用 var, for 里面调函数
for (var i = 0; i < 3; i++) {
    function test() {
        console.log(i); // 0 // 1 // 2
    }
    test();
}
for 循环里面用立即执行函数
for(var i = 0; i < 3; i++){
    (function test() {
        console.log(i); // 0 // 1 // 2
    })();
}
闭包结合 var 循环索引同步
for 内调函数, 同义变体
for(var i = 0; i < 3; i++){
    // function test(index) {
    //     console.log(index); // 0 // 1 // 2
    // }
    // test(i);
    (function test(index) {
        console.log(index); // 0 // 1 // 2
    })(i);
}

这样==就能==点第几个按钮就在控制台输出几 (从零开始)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>22-JavaScript-循环索引同步练习</title>
</head>
<body>
<button>我是按钮1</button>
<button>我是按钮2</button>
<button>我是按钮3</button>
<script>
    // 不能同步, 原理与 for 完调函数相同, for 才有 "每个 button 上都绑定了 onclick 事件", 此时 i 已经是 3
    /*
    let oBtns = document.querySelectorAll("button");
    for(var i = 0; i < oBtns.length; i++){
        let btn = oBtns[i];
        btn.onclick = function () {
            console.log(i); // 3
        }
    }
    */

    // 可以同步
    /*
    for(var i = 0; i < 3; i++){
        (function test(index) { // var index = i;
            console.log(index); // 0 1 2
        })(i);
    }
    */
    let oBtns = document.querySelectorAll("button");
    for(var i = 0; i < oBtns.length; i++) {
        let btn = oBtns[i];
        (function test(index) { // var index = i;
            // console.log(index); // 0 1 2
            // 注意点: onclick对应的方法由于满足了闭包的条件, 所以onclick对应的方法也是一个闭包
            btn.onclick = function () {
                console.log(index);
            }
        })(i);
    }
</script>
</body>
</html>

==这样又不行==, 个中差别只在于是否立即执行, 给参数赋值 (上面那种) 是 js 立即就干了的, 而触发点击事件却不是. 妈欸好鬼酷哦

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>22-JavaScript-循环索引同步练习</title>
  </head>
  <body>
    <button>我是按钮1</button>
    <button>我是按钮2</button>
    <button>我是按钮3</button>
    <script>
      // 不能同步
      let oBtns = document.querySelectorAll("button");
      for (var i = 0; i < oBtns.length; i++) {
        let btn = oBtns[i];
        (function test() {
          btn.onclick = function () {
            console.log(i);
          };
        })();
      }
    </script>
  </body>
</html>

let 天然可以循环索引同步

  • 在ES6中如果在循环中通过 let 定义的变量, 那么这个变量是一个==局部变量==
  • 由于 i 是局部变量, 所以每次执行完循环体都会重新定义一个 i 变量, 且这个 i 并不对之前的 i 进行覆盖, 相互之间并不相干
  • 在 ES6 中由于 {} 是块级作用域, 所以只要在块级作用域中定义了一个函数 并且这个函数中用到了块级作用域中的数据, 那么这个函数就是闭包
var list = [];
// 这里的i是全局变量
for(var i = 0; i < 3; i++){
    var fn = function test() {
        console.log(i); // 3 // 3 // 3
    }
    list.push(fn);
}

console.log(i); // 3
list[0]();
list[1]();
list[2]();
let list = [];
for (let i = 0; i < 3; i++) {
    let fn = function test() {
        console.log(i); // 0 // 1 // 2
    };
    list.push(fn);
}

// console.log(i); // Uncaught ReferenceError: list is not defined
list[0]();
list[1]();
list[2]();

var 与 let 搞笑区别

  • 在ES6中由于 {} 是块级作用域, 所以只要在块级作用域中定义了一个函数
  • 并且这个函数中用到了块级作用域中的数据, 这个函数就是闭包
for(var i = 0; i < 3; i++){ // 0 1 2 3
    function test() {
        console.log(i); // 3
    }
}
test();
for(let i = 0; i < 3; i++){
    function test() {
        console.log(i); // 2
    }
}
test();

闭包与 let, 优雅现代

  • {} 的块级只能通过 let 来达成, 用了 let 后 {} 包着的函数 prone to 形成闭包
  • 在ES6中
    1. for循环中通过let定义的变量是一个局部变量
    2. 2.for循环中通过let定义的变量每次执行循环体都会重新定义一个新的属于自己的变量
    3. for循环中如果定义了函数, 这个函数用到了通过let定义的变量, 那么这个函数是一个闭包
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>22-JavaScript-循环索引同步练习</title>
</head>
<body>
<button>我是按钮1</button>
<button>我是按钮2</button>
<button>我是按钮3</button>
<script>
    let oBtns = document.querySelectorAll("button");
    for(let i = 0; i < oBtns.length; i++){
        let btn = oBtns[i];
        btn.onclick = function () {
            console.log(i);
        }
    }
</script>
</body>
</html>