闭包

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

  • 闭包:函数和其周围状态(词法环境)的引用捆绑在一起形成闭包
    • 可以在另一个作用域中调用一个函数的内部函数并访问到该函数作用域中的成员
  • 闭包的本质: 函数在执行的时候会放到一个执行栈上当函数执行完毕后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能被释放,因此内部函数依然可以访问外部函数的成员

看下面这个代码的执行过程, 当我们调用 makeFunc1 的时候他里面会创建一个 名为 name 的变量, 当函数执行完毕的时候,函数内部的成员会被释放掉。如果这个函数里面又返回了一个函数,并且在返回的这个函数里又访问了外部函数的成员,其实这就是闭包。makeFunc2 其实就产生了闭包,当调用完 makeFunc2 的时候它会返回一个函数, myFunc 其实就引用了makeFunc2中返回的函数,当外部对内部有引用的时候makeFunc2 内部的成员就不会被释放, myFunc 依然可以访问 makeFunc2 中名为 name 的变量

function makeFunc1() {
  var name = "hellow closure";
}

makeFunc1()

function makeFunc2() {
  var name = "hellow closure";
  function displayName() {
      alert(name);
  }
  return displayName;
}

var myFunc = makeFunc2();
myFunc();

例子

在做项目中可能会出现多次求2次方或者3次方或者n次方的情况,Math.pow 可以求但是我们并不想重复的传入n次方的参数,所以我们把n次方函数抽离出来

Math.pow(4, 2)
Math.pow(5, 2)

function makePower(power) {
  // 返回了一个函数
  return function (number) {
    // 访问了外部函数的成员 power 
    return Math.pow(number, power)
  }
} 

// 求平方
let power2 = makePower(2)
let power3 = makePower(3)

console.log(power2(2))
console.log(power3(2))
console.log(power2(4))
console.log(power3(4))

调试闭包的方法

  • 浏览器打开我们需要调试的页面,打开开发者工具调到 sources 下找到我们需要调试的文件,在第一次调用的地方打上断点,刷新一下浏览器

image-20221017232204873

  • 当第一次执行到断点的时候观察开发者工具右边的位置
    • call stack 函数调用栈: 现在我们代码在script 标签里面,script标签里面的代码都是在一个匿名函数中调用的
    • scope 作用域:全局作用域就是我们的window对象

image-20221017232438071

  • 继续往下执行按f11跳进函数内部
    • call stack 函数调用栈:我们继续看 call stack 这个位置, 此时栈顶是makePower
    • scope 作用域:此时出现了一个local 也就是局部作用域,此时 makePower 的参数 power的值是 2

image-20221017232940042

  • 继续往下执行,此时makePower已经执行完了,在看call stack makePower 已经被移除了,我们开始说过函数执行完了会从执行栈上移除,同时作用域scope也被移除,看不到了,此时多了一个Script 里面有一个power2,这个power2是我们用let 定义的 power2变量。let 定义的会挂载到Script上, 通过 var 定义的变量会在全局属性上

image-20221017233520436

  • 接下来我们调试求平方的函数,在进入函数中的时候我们发现产生了新的局部作用域,此时下面多了一个Closure,这个就是闭包相关的变量,里面有个power 他的值依然在内存中存在,通过控制台调试我们可以清楚地看到闭包发生的位置

image-20221017234201121