你会如何向了解它们所包含的概念(例如函数、变量等)但不理解闭包本身的人解释 JavaScript 闭包?
网友回答:
JavaScript 中的每个函数都维护一个指向其外部词法环境的链接。词法环境是作用域内所有名称(例如变量、参数)及其值的映射。
因此,只要您看到关键字,该函数中的代码都可以访问在函数外部声明的变量。function
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);
这将记录,因为函数关闭参数和变量,两者都存在于外部函数的词法环境中。16
bar
x
tmp
foo
函数,连同它与函数的词汇环境的联系是一个闭包。bar
foo
函数不必返回即可创建闭包。仅仅凭借其声明,每个函数都在其封闭的词汇环境中关闭,形成一个闭包。
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + (++tmp)); // will also log 16
}
}
var bar = foo(2);
bar(10); // 16
bar(10); // 17
上面的函数也会记录 16,因为里面的代码仍然可以引用参数和变量,即使它们不再直接在范围内。bar
x
tmp
但是,由于仍然在 的闭合中徘徊,因此可以增加。每次您调用 时,它都会递增。tmp
bar
bar
闭包最简单的例子是:
var a = 10;
function test() {
console.log(a); // will output 10
console.log(b); // will output 6
}
var b = 6;
test();
调用 JavaScript 函数时,将创建新的执行上下文。与函数参数和目标对象一起,此执行上下文还接收指向调用执行上下文的词法环境的链接,这意味着在外部词法环境中声明的变量(在上面的示例中,两者和)都可以从 获得。ec
a
b
ec
每个函数都会创建一个闭包,因为每个函数都有一个指向其外部词法环境的链接。
请注意,变量本身在闭包中可见,而不是副本。
网友回答:
闭包是以下各项的配对:
词法环境是每个执行上下文(堆栈帧)的一部分,是标识符(即局部变量名称)和值之间的映射。
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 中,函数带有对私有“状态盒”的引用,只有它们(以及在同一词汇环境中声明的任何其他函数)才能访问该框。这个状态框对函数的调用方是不可见的,为数据隐藏和封装提供了一种出色的机制。
请记住:JavaScript 中的函数可以像变量(一等函数)一样传递,这意味着这些功能和状态的配对可以在您的程序中传递:类似于在 C++ 中传递类的实例的方式。
如果 JavaScript 没有闭包,那么必须在函数之间显式传递更多状态,从而使参数列表更长,代码噪声更大。
因此,如果您希望函数始终能够访问私有状态,则可以使用闭包。
…我们经常希望将状态与函数相关联。例如,在 Java 或 C++ 中,当您向类添加私有实例变量和方法时,您将状态与功能相关联。
在 C 和大多数其他常用语言中,函数返回后,所有局部变量都不再可访问,因为堆栈帧被销毁。在 JavaScript 中,如果你在另一个函数中声明一个函数,那么外部函数的局部变量在从它返回后仍然可以访问。这样,在上面的代码中,函数对象在从 返回后仍然可用。secret
inner
foo
每当需要与函数关联的私有状态时,闭包都很有用。这是一个非常常见的场景 – 请记住:JavaScript 直到 2015 年才有类语法,它仍然没有私有字段语法。闭包满足了这一需求。
在下面的代码中,该函数关闭汽车的详细信息。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
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())
此示例显示局部变量未在闭包中复制:闭包维护对原始变量本身的引用。就好像即使在外部函数退出后,堆栈帧在内存中仍保持活动状态。
function foo() {
let x = 42
let inner = () => console.log(x)
x = x + 1
return inner
}
foo()() // logs 43
在下面的代码中,三个方法 、 和 都关闭在同一词法环境中。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
如果使用使用 声明的变量,请注意要了解要关闭的变量。声明使用的变量被吊起。这在现代 JavaScript 中的问题要小得多,因为引入了 和 。var
var
let
const
在下面的代码中,每次循环时,都会创建一个新函数,该函数在 .但是由于被吊在循环之外,所有这些内部函数都关闭在同一变量上,这意味着 (3) 的最终值被打印三次。inner
i
var i
i
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(…)
网友回答:
前言:这个答案是在提问时写的:
就像老阿尔伯特说的:“如果你不能向一个六岁的孩子解释,你自己真的不明白。好吧,我试图向一位 27 岁的朋友解释 JS 闭包,但完全失败了。
有人可以认为我6岁并且对这个主题非常感兴趣吗?
我很确定我是唯一一个试图从字面上理解最初问题的人之一。从那以后,这个问题已经发生了几次变化,所以我的答案现在可能看起来非常愚蠢和不合适。希望这个故事的总体想法对某些人来说仍然很有趣。
在解释困难的概念时,我非常喜欢类比和隐喻,所以让我尝试一个故事。
从前:
有一位公主…
function princess() {
她生活在一个充满冒险的美好世界里。她遇到了她的白马王子,骑着独角兽环游她的世界,与龙战斗,遇到了会说话的动物,以及许多其他奇幻的事情。
var adventures = [];
function princeCharming() { /* ... */ }
var unicorn = { /* ... */ },
dragons = [ /* ... */ ],
squirrel = "Hello!";
/* ... */
但她总是要回到她沉闷的家务和成年人的世界。
return {
她经常告诉他们她最近作为公主的惊人冒险。
story: function() {
return adventures[adventures.length - 1];
}
};
}
但他们看到的只是一个小女孩……
var littleGirl = princess();
…讲述有关魔法和幻想的故事。
littleGirl.story();
即使大人们知道真正的公主,他们也永远不会相信独角兽或龙,因为他们永远看不到它们。大人们说,他们只存在于小女孩的想象中。
但我们知道真正的真相;那个小女孩里面有公主…
…真的是一个公主,里面有一个小女孩。
模板简介:该模板名称为【我想知道JavaScript 闭包是如何工作的?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【JavaScript】栏目查找您需要的精美模板。