前言

闭包这东西都讲烂了,随便一搜就有大堆的文章在说,之所以在写一遍也是为了能加深记忆做好总结。平时用的时候就直接写,但出去面试的被问到的时候却不知道如何回答的周全,那怎么回答算是OK的呢?

来看下MDN定义:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域,通俗一点就是函数里面在嵌套函数。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

如果被问到的话就可以回答上述内容,在加深一点的话就是,函数在执行的会放执行栈中,当函数执行完成之后就会从栈中移除掉,但是如果使用是闭包的话,堆中作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数成员。

闭包

看下面代码深刻一下印象:

function makeFunc() {
  var name = "Mozilla"; // name 是一个被 makeFunc 创建的局部变量
  function displayName() { // displayName() 是内部函数,一个闭包
      alert(name); // 使用了父函数中声明的变量
  }
  return displayName;
}
var myFunc = makeFunc();
myFunc();

makeFunc() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 makeFunc() 里的内部函数,并且仅在 makeFunc() 函数体内可用。请注意,displayName() 没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 makeFunc() 中声明的变量 name

如果对闭包了解不深的情况下,以为执行了makeFunc() 之后会被移除,其实不然。在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert中。

内存泄漏

闭包如果在处理不当时,会造成内存泄漏,也就是说垃圾回收机制无法被销毁,最常见的是DOM元素形成的闭包。看下面例子

function closure(){
  // div用完之后一直存在内存中,无法被回收
  var div = document.getElementById('div');
  div.onclick = function () {
    console.log(div.innerHTML);// 这里用oDiv导致内存泄露
  };
}

// 解除引用避免内存泄露
function closure(){    
  var div = document.getElementById('div');    
  var test = div.innerHTML;
  div.onclick = function () {
    console.log(test);
  };
  div = null;
}

闭包的常见应用

节流简述,就是防止在一段时间内重复执行,相当于一个开关锁?

var throttle = function(fn, delay) {
  var time = null
  return function() {
    var self = this
    if(!time) {
      time = setTimeout(function(){
        fn.apply(self)
        time = null
      }, delay)
    }
  }
}

防抖简述,就是重复执行时,清除之前的,一直使用的是最后一个。

var throttle = function(fn, delay) {
  var time = null
  return function() {
    var self = this
    clearTimeout(time)
    time = setTimeout(function(){
      fn.apply(self)
    }, delay)
  }
}

once 一次执行的函数,通过返回一个函数,等第一次调用之后设置状态,下次调用获取状态再 return 掉。

function once (fn) {
  let called = false
  return function () {
    if (called) {
      return
    }
    called = true
    fn.apply(this, arguments)
  }
}

总结几点

  • 形成:函数中嵌套函数
  • 作用:函数内部调用外部变量、构造函数的私有属性、延长变量生命周期
  • 优点:希望一个变量长期存在内存中、模块化代码避免全局变量的污染、私有属性
  • 缺点:无法回收闭包中引用变量,容易造成内存泄漏

函数科里化

接着上面的闭包,其实科里化可以说是闭包的基础实现,比如说之前经常出现的题:
实现一个函数,使 sum(1)(2) 得 3

function sum(x) {
  return function(y) {
    return x + y;
  };
}
var s = sum(1)(2)
console.log(s) // 3

// 不使用科里化来得到两个数的和
function sum(x, y) {
  return x + y;
}
var s = sum(1, 2)
console.log(s) // 3

有什么区别吗?看起来相差也不大,但是为什么要使用上面的写法呢,