这个问题的答案是community effort .编辑现有答案以改进这篇文章。它目前不接受新的答案或互动。
你会如何向了解 JavaScript 闭包组成的概念(例如函数、变量等)但不了解闭包本身的人解释 JavaScript 闭包?
我见过the Scheme example在维基百科上给出,但不幸的是它没有帮助。
最佳答案
闭包是一对:
词法环境是每个执行上下文(堆栈帧)的一部分,是标识符(即局部变量名称)和值之间的映射。
JavaScript 中的每个函数都维护对其外部词法环境的引用。此引用用于配置调用函数时创建的执行上下文。此引用使函数内部的代码能够“查看”函数外部声明的变量,而不管函数何时何地被调用。
如果一个函数被一个函数调用,而该函数又被另一个函数调用,那么就会创建一个指向外部词法环境的引用链。这个链称为作用域链。
在以下代码中,
inner
用在 foo
时创建的执行上下文的词法环境形成一个闭包被调用,关闭变量 secret
:function foo() {
const secret = Math.trunc(Math.random()*100)
return function inner() {
console.log(`The secret number is ${secret}.`)
}
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`
换句话说:在 JavaScript 中,函数携带对私有(private)“状态框”的引用,只有它们(以及在同一词法环境中声明的任何其他函数)才能访问该引用。这个状态框对于函数的调用者是不可见的,为数据隐藏和封装提供了一个很好的机制。
请记住:JavaScript 中的函数可以像变量(一等函数)一样传递,这意味着这些功能和状态的配对可以在您的程序中传递:类似于您在 C++ 中传递类的实例的方式。
如果 JavaScript 没有闭包,则必须在函数之间显式传递更多状态,从而使参数列表更长且代码更嘈杂。
因此,如果您希望函数始终可以访问私有(private)状态,则可以使用闭包。
...而且经常我们确实希望将状态与函数相关联。例如,在 Java 或 C++ 中,当您向类添加私有(private)实例变量和方法时,您将状态与功能相关联。
在 C 和大多数其他常见语言中,在函数返回后,所有局部变量都不再可访问,因为堆栈帧被破坏。在 JavaScript 中,如果你在另一个函数中声明一个函数,那么外部函数的局部变量在从它返回后仍然可以访问。这样,在上面的代码中,
secret
保持对函数对象可用 inner
, 从 foo
返回后.闭包的使用
当您需要与函数关联的私有(private)状态时,闭包很有用。这是一个非常常见的场景 - 请记住:JavaScript 直到 2015 年才有类语法,而且它仍然没有私有(private)字段语法。闭包满足了这种需求。
私有(private)实例变量
在下面的代码中,函数
toString
关闭汽车的细节。function Car(manufacturer, model, year, color) {
return {
toString() {
return `${manufacturer} ${model} (${year}, ${color})`
}
}
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())
函数式编程
在下面的代码中,函数
inner
两者均收盘 fn
和 args
.function curry(fn) {
const args = []
return function inner(arg) {
if(args.length === fn.length) return fn(...args)
args.push(arg)
return inner
}
}
function add(a, b) {
return a + b
}
const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5
面向事件的编程
在以下代码中,函数
onClick
关闭变量 BACKGROUND_COLOR
.const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'
function onClick() {
$('body').style.background = BACKGROUND_COLOR
}
$('button').addEventListener('click', onClick)
<button>Set background color</button>
模块化
在以下示例中,所有实现细节都隐藏在立即执行的函数表达式中。功能
tick
和 toString
关闭他们完成工作所需的私有(private)状态和功能。闭包使我们能够模块化和封装我们的代码。let namespace = {};
(function foo(n) {
let numbers = []
function format(n) {
return Math.trunc(n)
}
function tick() {
numbers.push(Math.random() * 100)
}
function toString() {
return numbers.map(format)
}
n.counter = {
tick,
toString
}
}(namespace))
const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())
例子
示例 1
这个例子表明局部变量没有在闭包中复制:闭包维护对原始变量本身的引用。就好像堆栈帧在外部函数退出后仍然存在于内存中一样。
function foo() {
let x = 42
let inner = function() { console.log(x) }
x = x+1
return inner
}
var f = foo()
f() // logs 43
示例 2
在下面的代码中,三个方法
log
, increment
, 和 update
都关闭在同一个词法环境中。并且每次
createObject
被调用,一个新的执行上下文(堆栈帧)被创建,一个全新的变量 x
,并创建了一组新的函数(log
等),它们关闭了这个新变量。function createObject() {
let x = 42;
return {
log() { console.log(x) },
increment() { x++ },
update(value) { x = value }
}
}
const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42
示例 3
如果您使用的是使用
var
声明的变量,请注意您了解要关闭的变量。使用 var
声明的变量被吊起。由于 let
的引入,这在现代 JavaScript 中的问题要小得多。和 const
.在下面的代码中,每次循环,一个新函数
inner
已创建,结束于 i
.但是因为var i
被提升到循环外,所有这些内部函数都关闭同一个变量,这意味着 i
的最终值(3) 打印,三遍。function foo() {
var result = []
for (var i = 0; i < 3; i++) {
result.push(function inner() { console.log(i) } )
}
return result
}
const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
result[i]()
}
最后的要点:
function
从另一个函数内部是闭包的经典例子,因为外部函数内部的状态对于返回的内部函数是隐式可用的,即使在外部函数完成执行之后也是如此。 eval()
在函数内部,使用了闭包。文你eval
可以引用函数的局部变量,在非严格模式下,甚至可以使用eval('var foo = …')
创建新的局部变量. new Function(…)
( Function constructor )在函数内部,它不会关闭其词法环境:而是关闭全局上下文。新函数不能引用外部函数的局部变量。 链接
关于javascript - JavaScript 闭包是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/111102/