目录

🍎 JavaScript 闭包

相关问题:

  • 对闭包的理解?
  • 闭包的使用场景?

# 概念解释

  • 闭包:一个函数以及其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合称为闭包。
  • 闭包的产生:一个嵌套的内部函数调用了嵌套的外部函数的变量,就产生了闭包。闭包存在于嵌套的内部函数中。
  • 闭包的理解:
    • 闭包是嵌套的内部函数;
    • 闭包是包含被引用变量(函数)的对象;

简单地说,闭包是可以使内层函数访问到其外层函数的作用域

产生闭包的条件:

  • 函数嵌套;
  • 内部函数引用了外部函数的数据。所以执行外部函数定义就会产生闭包,不必调用内部函数。

🌰 例子:

function init() {
  var name = "Mozila";
  function displayName() {
    alert(name);
  }
  displayName();
}

init()
1
2
3
4
5
6
7
8
9

displayName 函数没有自己的局部变量,但是由于闭包的特性,可以访问到函数外部的变量

常见的闭包:

🌰 例子 / 将函数作为另一个函数的返回值:

function fn1() {
  var a = 2
  function fn2() {
    a++
    console.log(a)
  }
  return fn2
}

var f = fn1()
f()
f()
1
2
3
4
5
6
7
8
9
10
11
12

🌰 例子 / 将函数作为实参传递给另一个函数调用:

function showDelay(msg, time) {
  setTimeout(function() {
    console.log(msg)
  }, time)
}


showDelay('msg', 1000)
1
2
3
4
5
6
7
8

# 闭包的生命周期

  • 闭包的产生:在嵌套内部函数定义执行完就产生了(不是调用时)例如上面例子中 fn1() 已经定义完,函数提升,内部函数已经创建了。
  • 闭包的死亡:在嵌套的内部函数称为垃圾对象时(包含闭包的函数称为垃圾对象))(例如上面的例子,可以直接 f = null

# 闭包的使用场景

使用场景特点:

  • 创建私有变量;
  • 延长变量的生命周期。 使用函数内部的变量在函数执行完后,仍然存活在内存中。

一般函数的词法环境会在函数返回后就被销毁,而闭包会保存对创建时所在的词法环境的引用,即便创建时所在的执行上下文被销毁,但是创建时的词法环境依然存在,以达到延长变量的生命周期的目的。

如例子 var f = fn1() 。当存在闭包是,函数内部声明的局部变量被包含在闭包内,由于外部声明了一个变量把内部的一个闭包制止关联着,所以闭包会一直存在。

相关使用场景的例子:

🌰 例子 / 调整字号按钮:

function makeSizer(size) {
  return function() {
    document.body.style.fontStyle = size + 'px'
  }
}

var size12 = makeSizer(12)
var size14 = makeSizer(14)
var size16 = makeSizer(16)

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
1
2
3
4
5
6
7
8
9
10
11
12
13

🌰 例子 / 柯里化函数:

柯里化的目的在于避免频繁调用具有相同参数的函数的同时,又能轻松重用函数。

// 获取矩形面积的函数
function getArea(width, height) {
  return width * height;
}

const area1 = getArea(10, 20)
const area1 = getArea(10, 30)
const area1 = getArea(10, 40)

// 可以使用闭包柯里化这个函数
function getArea(width) {
  return height => {
    return width * height
  }
}

const getTenWidthArea = getArea(10)
const area1 = getTenWidthArea(20) // 相当于 getArea(10,20)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

🌰 例子 / 使用闭包模拟私有方法:

JavaScript 中不支持声明私有变量,可以通过 闭包 实现模拟私有方法。

var makeCounter = (function() {
  var privateCounter = 0;

  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1)
    },
    value: function() {
      return privateCounter
    }
  }
})();

var Counter1 = makeCounter
var Counter2 = makeCounter
console.log(Counter1.value())
Counter1.increment()
console.log(Counter1.value())
Counter1.decrement()
console.log(Counter1.value())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

上述通过使用闭包来定义公共函数,并令其可以访问私有函数和变量,这种方式也叫模块方式。

两个计数器 Counter1Counter2 是维护它们各自的独立性的,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境,不会影响另一个闭包中的变量。

🌰 其他例子:例如延迟调用、回调等闭包的应用,核心思想还是差 u 你姑姐爱你私有变量和延长变量的生命周期。

# 闭包的缺点和解决

缺点:函数在执行之后,函数内部的局部变量没有及时释放,占用内存的时间变长,导致内存泄漏。

解决:能不使用闭包就不使用闭包;及时释放使用过的闭包。

# 闭包的注意事项

如果不是某些特定的任务需要使用闭包,在其他函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本的性能具有负面影响。

🌰 例子 / 创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造其中。

function MyObject(name, message) {
  this.name = name.toString()
  this.message = message.toString()
  this.getName = function() {
    return this.name
  }
  this.getMessage = function() {
    return this.message
  }
}
1
2
3
4
5
6
7
8
9
10

此处应该修改为 原型,因为此处并没有应用闭包的优点,应该避免使用闭包:

function MyObject(name, message) {
  this.name = name.toString()
  this.message = message.toString()
}

MyObject.prototype.getName = function () {
  return this.name
}

MyObject.prototype.getMessage = function () {
  return this.message
}
1
2
3
4
5
6
7
8
9
10
11
12

# 参考

📢 上次更新: 2022/09/02, 10:18:16