探索js闭包的由来与应用场景

592 阅读3分钟

一句话闭包

  • 闭包允许函数访问并操作函数外部的变量 --JS 忍者秘籍
  • 闭包是指有权访问另外一个函数作用域中的变量的函数 -- 红宝书
  • 闭包是指那些能够访问自由变量的函数,这里的自由变量是外部函数作用域中的变量 -- MDN

作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域主要两种:全局作用域和函数作用域。
其次:块级作用域
先看下面一段代码

  // 全局作用域
  var a = 1;
  var f = 5;

  function fu() {// 函数作用域
    var a = 2;
    var b = 3;
    console.log(a, f); // 2, 5
  }

  fu();
  console.log(a); //  1
  // console.log(b); //  b is not defined

  if (true) {// 块级作用域
    var c = 5;
    let d = 6;
  }
  console.log(c); //  5
  // console.log(d); //  d is not defined

我们通过上面这段代码,发现Javascript语言的一些特殊之处:

  • 函数内部可以直接读取全局变量
  • 函数外部无法读取函数内的局部变量(b is not defined)
  • 块级作用域内通过let、const创建的变量在外部无法读取, 详解(d is not defined)

作用链

当可执行代码内部访问变量时,会先查找本地作用域,如果找到目标变量即返回,否则会去父级作用域继续查找...一直找到全局作用域, 没找到则报错 XXX is not defined

闭包的形成

先看一下这个列子 列1:

    var b =13
    function foo(){
        var b =14
        return function fo(){
            console.log(b) 
        }
    }
    foo()() // 14

我们可能会觉得很奇怪,上面的代码不是相当于下面这样子么? 列2:

    var b =13
    function fo(){
            console.log(b) 
    }
    fo() // 13

我们在看下这列子,发现与列2同理。 列3:

    var b =13
    function foo(){
        return function fo(){
            console.log(b) 
        }
    }
    foo()(); // 13

解析下以红宝书为列:闭包是指有权访问另外一个函数作用域中的变量函数
画下重点:

  • 访问另外一个函数作用域中的变量
  • 是一个函数 从中可得列1是一个闭包
    var b =13
    function foo(){
        var b =14
        return function fo(){ // 一个函数
            // 这里访问了另外一个函数作用域中的变量(访问了foo函数作用域的变量b)
            console.log(b) 
        }
    }
    foo()() // 14

闭包的作用

实现模块化,保护私有变量不被外部侵扰。
常见方法使用自执行函数实现闭包模块化

// 自执行函数实现模块化
// 模块1
(function () {
    var a = 1;
    console.log(a); // 1
})();
// 模块2
(function () {
    var a = 2;
    console.log(a); // 2
})();

模块1、模块2的a变量互不侵扰。

闭包的问题

    function father(){
        var b =14
        return function child(){
            console.log(b) 
        }
    }
    foo()() // 14

解析下上面闭包代码, father 函数作用域隔绝了外部环境,所有变量引用都在函数内部完成,father 运行完成以后,内部的变量就应该被销毁,内存被回收。然而闭包导致了全局作用域始终存在一个 child 函数在引用着 father 内部的 b 变量,这就意味着 father 内部定义的 child 函数引用数始终为 1,垃圾运行机制就无法把它销毁。引擎无法判断你什么时候还会调用闭包函数,只能一直让这些数据占用着内存, 从而导致 内存泄露

内存泄露 是指当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或者内存池的现象。内存泄漏可能会导致应用程序卡顿或者崩溃。

经典面试题

问:分析它实际运行的结果

    for (var i = 0; i < 3; i++) { 
        setTimeout(() => console.log(i),0); 
    }
    // 3 3 3

问:改造它,输出0 1 2
1.es6

    for (let i = 0; i < 3; i++) { 
        setTimeout(() => console.log(i),0)
    }
    // 0 1 2

2.setTimeout第三个参数

    for (var i = 0; i < 3; i++) { 
        setTimeout((t) => console.log(t),0,i)
    }
    // 0 1 2

3.闭包

    for (var i = 0; i < 3; i++) { 
       (function () {console.log(i)})()
    }
    // 0 1 2

相关参考

juejin.cn/post/684490…