闭包

130 阅读4分钟

闭包

《JavaScript高级程序设计》:

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

《JavaScript权威指南》:

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

《你不知道的JavaScript》

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

闭包就是那些引用了外部作用域中变量的函数。

外部作用域执行完毕后

当外部作用域执行完毕后,内部函数还存活(仍在其他地方被引用)时,闭包才真正发挥其作用。譬如以下几种情况:

  • 在异步任务例如timer定时器,事件处理,Ajax请求中被作为回调
  • 被外部函数作为返回结果返回,或者返回结果对象中引用该内部函数

考虑如下的几个示例:

第一种异步任务情况

Timer

(function autorun(){
    let x = 1;
    setTimeout(function log(){
      console.log(x);
    }, 10000);
})();

变量x将一直存活着直到定时器的回调执行或者clearTimeout()被调用。 如果这里使用的是setInterval(),那么变量x将一直存活到clearInterval()被调用。

Event

(function autorun(){
    let x = 1;
    $("#btn").on("click", function log(){
      console.log(x);
    });
})();

当变量x在事件处理函数中被使用时,它将一直存活直到该事件处理函数被移除。

Ajax

(function autorun(){
    let x = 1;
    fetch("http://").then(function log(){
      console.log(x);
    });
})();

变量x将一直存活到接收到后端返回结果,回调函数被执行。

在已上几个示例中,我们可以看到,log()函数在父函数执行完毕后还一直存活着,log()函数就是一个闭包。

第二种函数返回情况

函数作为返回值

function fn() {
  var max = 10;
  return function bar(x) {
    if (x > max) {
      console.log(x)
    }
  }
}
var f1 = fn();
f1(15);

函数作为参数被传递

var max = 10;
var fn = function (x) {
  if (x > max) {
    console.log(x);
  }
};

(function (f) {
  var max = 100;
  f(15);
})(fn)

闭包的作用

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

2、私有化变量

function base() {
  let x = 10; // 私有变量
  return {
    getX: function () {
      return x;
    }
  }
}
let obj = base();
console.log(obj.getX());

3、模拟块级作用域

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = (function (j) {
    return function () {
      console.log(j)
    }
  })(i)
}
a[6]();

4、创建模块

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

  • 必须有外部的封闭函数,该函数必须至少被调用一次 ( 每次调用都会创建一个新的模块实例 )
  • 封闭函数必须返回至少个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
function coolModule() {
  let name = 'XiaoYang';
  let age = 20;
  function sayName() {
    console.log(name);
  }
  function sayAge() {
    console.log(age);
  }
  return {
    sayName,
    sayAge
  }
}

let info = coolModule();
info.sayName();

测试

function createCounter() {
   let counter = 0
   const myFunction = function() {
     counter = counter + 1
     return counter
   }
   return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3)

闭包与循环

经典面试题,循环中使用闭包解决 var 定义函数的问题

闭包只存储外部变量的引用,而不会拷贝这些外部变量的值。 

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
}

console.log(new Date, i);

不会是期望的 5-> 0,1,2,3,4,而是 5->5,5,5,5,5 即第 1 个 5 直接输出,1 秒之后,输出 5 个 5

1、使用闭包解决

for (var i = 0; i < 5; i++) {
    (function(j) {  // j = i
        setTimeout(function() {
            console.log(new Date, j);
        }, 1000);
    })(i);
}

console.log(new Date, i);

2、使用 setTimeout 的第三个参数

for (var i = 0; i < 5; i++) {
    setTimeout(function(j) {
        console.log(new Date, j);
    }, 1000, i);
}

console.log(new Date, i);

3、使用参数传递

var output = function (i) {
  setTimeout(function() {
    console.log(new Date, i);
  }, 1000);
};

for (var i = 0; i < 5; i++) {
  output(i);  // 这里传过去的 i 值被复制了
}

console.log(new Date, i);

4、使用 let

for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
}

console.log(new Date, i); // 这种其实会报错,因为 let i 这里访问不到 i

具体看参考2

参考

理解闭包

从闭包说起 这个很好

闭包的强大威力