闭包(closure)

闭包是函数和声明该函数的词法环境的组合。MDN

MDN上的栗子
function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();
  • JavaScript中的函数会形成闭包。 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。
  • myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用,而 displayName 实例仍可访问其词法作用域中的变量,即可以访问到 name 。由此,当 myFunc 被调用时,name 仍可被访问,其值 Mozilla 就被传递到alert中。

为什么要使用闭包?

局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。

怎么样使用闭包?

  • 定义外层的函数(outer),封装被保护的局部变量;
  • 定义内层的函数(inner),执行对外层函数变量的引用;
  • 外层函数(outer)返回内层(inner)的函数对象,并且外层函数(outer)被调用,结果保存在一个全局的变量中。
注意一:手动解除引用
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

// 释放对闭包的引用
add5 = null;
add10 = null;

add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。最后通过 null 释放了 add5 和 add10 对闭包的引用。

在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;而makeAdder对象被全局变量add5和add10引用,就会占用内存空间。

注意二:闭包遇上for循环

闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。

function test() {
  var arr = [];
  for(var i = 0;i < 10;i++) {
    arr[i] = function() {
      return i;
    };
  }
  for(var a = 0;a < 10;a++) {
    console.log(arr[a]());
  }
}
test();  // 连续打印10个10

讨论区链接

函数1作用域
for(var i = 0; i < 10; i++) { 函数1作用域
        我在函数1作用域中
        arr[i] = function() { 函数2作用域
          我在函数2作用域中
          return i;
        };
}
函数1作用域
console.log(i);

console.log(i); 执行到这里的时候,i 是10,既然这里是10,那么在函数2作用域中访问i也是10,因为函数2作用域中没有,向上去函数1作用域中找,同一作用域中同一变量名的变量值肯定是相同的(未修改的情况下)。

当你用 let 的时候,如下:

块1作用域
for(let i = 0; i < 10; i++) { 块2作用域
    我在块2作用域中
    console.log(i); // 毫无疑问,这里的i从0依次增加到10  
    arr[i] = function() { 块3作用域
      我在块3作用域中
      return i;
    };
}
块1作用域

当你换成 let 的时候,读取i的时候,在当前作用域块3中没有找到,向上一个作用域块2寻找,在块2作用域中发现i,于是拿到值。

闭包的缺点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value)。
function Animal() {
  
  // 私有变量
  var series = "哺乳动物";
  function run() {
    console.log("Run!!!");
  }
  
  // 特权方法
  this.getSeries = function() {
    return series;
  };
}

思考题

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);        // ?

var b = fun(0).fun(1).fun(2).fun(3);  // ?

var c = fun(0).fun(1);  // ?
c.fun(2);        // ?
c.fun(3);        // ?

参考文章

https://zhuanlan.zhihu.com/p/27857268
https://juejin.im/entry/57d60f7067f3560057e37e25
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。