什么是闭包

142 阅读3分钟

定义:MDN文档的解释是“函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)”。

其实,这句话的意思是闭包可以让你从内部函数访问外部函数作用域,由于在JavaScript中只有子函数可以访问其父函数内部的局部变量,因此我们可以简单把它定义为函数内部的函数及其可以访问到的局部变量环境共同构成了闭包,该函数就是一个闭包函数。

词法环境

let n=2
function f1(){
    let m=4
    p=6
    return n+m+p
}

在上面f1()函数中,申明n,m,p变量时就确定了他们能被访问的位置,这就是词法环境,变量n为全局变量,可被函数内外部引用,而变量m是在函数内部申明的,是局部变量,而不能在函数外部引用,但是变量p是一个全局变量,因为其没有用let/var来申明,相当于window.p可以被函数外部引用。

闭包

function f1(){
    let n=1
    let m=2
    function f2(){
        alert(n+m)
    }
}
f1();//3

讲清楚了词法环境,也就是变量作用域,我们就可以确定什么是闭包函数了,上面的f2函数就是一个闭包函数,他可以引用其父级函数f1内部的变量,换句话说f2引用了f2外部的变量。f2与其可访问的外部局部变量环境就构成了一个闭包结构。

闭包的用途

  1. 形成交互式页面

假入这里有一个HTML文档,并且里有三个按钮(#redButton,#greenBtton,#blueBtton)

    <button type="button" id="redButton">红色背景</button>
    <button type="button" id="greenButton">绿色背景</button>
    <button type="button" id="blueButton">蓝色背景</button>

那么创建一个bg函数,red,green,blue就是三个相同本体的闭包函数,只是改变了他们的变量而已,这样就可以手动控制页面背景颜色了。

function bg(color) {
  return function(){
    document.body.style.backgroundColor = color
  }
}

let red = bg('red')
let green = bg('green')
let blue = bg('blue')

redButton.onclick=red
greenButton.onclick = green
blueButton.onclick = blue
  1. 封装数据

下面是是一个计数器原理

let makeCounter = function() {
  let privateCounter = 0
  function changeBy(val) {
    privateCounter += val
  }
  return {
    increment: function() {
      changeBy(1)
    },
    decrement: function() {
      changeBy(-1)
    },
    value: function() {
      return privateCounter
    }
  }  
};

let Counter1 = makeCounter();
let Counter2 = makeCounter();
console.log(Counter1.value()); //0
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); //2
Counter1.decrement();
console.log(Counter1.value()); //1
console.log(Counter2.value()); //0

makeCounter()的返回值是一个对象,里面有increment()/decrement()/vaule()等API,changeBy函数就是一个闭包,每次调用这些API时就改变变量环境,因此闭包在封装数据、封装对象具有好处。

闭包的缺点

  1. 性能消耗大

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

  1. 在循环中创建闭包,var的弊端

当闭包在一个循环里,类似哈希表遍历时,不要使用var来申明每个变量,因为var的缺陷会导致变量提升带来不是预期的结果,使用let来申明变量。

ps:闭包会在父函数外部,改变父函数内部变量的值,把父函数当做一个对象,闭包当做返回值(类似jQuery封装对象里的Api),变量当做自身属性,这时就不能轻易改变父函数里面的变量