# 9. 什么是闭包?闭包的作用是什么?

# 闭包的定义

《JavaScript高级程序设计》:

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

《JavaScript权威指南》:

从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。

《你不知道的JavaScript》

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

# 闭包特性

  • (1)函数内再嵌套函数
  • (2)内部函数可以引用外层的参数和变量
  • (3)参数和变量不会被垃圾回收机制回收

创建一个闭包

function foo() {
    var a = 2;
    return function fn() {
        console.log(a);
    }
}
let func = foo();
func(); //输出2

闭包使得函数可以继续访问定义时的词法作用域。拜 fn 所赐,在 foo() 执行后,foo 内部作用域不会被销毁。

# 闭包的作用

  1. 能够访问函数定义时所在的词法作用域(阻止其被回收)。

  2. 私有化变量

function base() {
    let x = 10; //私有变量
    return {
        getX: function() {
            return x;
        }
    }
}
let obj = base();
console.log(obj.getX()); //10
  1. 模拟块级作用域
var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = (function(j){
        return function () {
            console.log(j);
        }
    })(i);
}
a[6](); // 6
  1. 创建模块
function coolModule() {
    let name = 'Yvette';
    let age = 20;
    function sayName() {
        console.log(name);
    }
    function sayAge() {
        console.log(age);
    }
    return {
        sayName,
        sayAge
    }
}
let info = coolModule();
info.sayName(); //'Yvette'

模块模式具有两个必备的条件(来自《你不知道的JavaScript》)

  • 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

鄙视题:

function fun(n,o) {
  console.log(o);
  return {
    fun: function(m) {
      return fun(m,n);
    }
  };
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
// 问: 三行a,b,c的输出分别是什么? 

解答

  • 1、第一行a

var a = fun(0); a.fun(1); a.fun(2); a.fun(3); 可以得知,第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,所以:

第后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。

遂:

在第一次调用fun(0)时,o为undefined;

第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;

第三次调用fun(2)时m为2,但依然是调用a.fun,所以还是闭包了第一次调用时的n,所以内部调用第一层的fun(2,0);所以o为0

第四次同理;

即:最终答案为undefined,0,0,0

  • 2、第二行b

var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,? 先从fun(0)开始看,肯定是调用的第一层fun函数;而他的返回值是一个对象,所以第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。

遂:

在第一次调用第一层fun(0)时,o为undefined;

第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;

第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)所以n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),所以o为1;

第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);所以o为2;

即最终答案:undefined,0,1,2

  • 3、第三行c

var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,? 根据前面两个例子,可以得知:

fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,所以c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的也是fun(1)返回的第二层fun函数。

遂:

在第一次调用第一层fun(0)时,o为undefined;

第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;

第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);所以o为1;

第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),所以o还为1

即最终答案:undefined,0,1,1