循环内存管理中的 JavaScript 闭包和块作用域变量

问题描述 投票:0回答:1

这个问题是几个不同的(我认为相关的)问题,我在下面给出,但总的来说,我试图理解 David Flanagan 的 O'Reilly JavaScript 书中的一段代码,该书中给出了一个使用循环创建闭包的示例无意中:

function constfuncs() {
    let funcs = [];
    for(var i = 0; i < 10; i++) {
        funcs[i] = () => i;
    }
    return funcs;
}

let funcs = constfuncs();
funcs[5]() //10, rather than 5

上面的代码示例旨在让

funcs
数组的每个元素成为一个函数,可以在不带参数的情况下调用该函数,以返回一个在数字上等于其在
funcs
中的索引的值;然而,它并没有成功做到这一点。我想我很大程度上理解为什么会这样:
var i
,因为它是使用
var
声明的,其作用域是函数
constfuncs
,而不是那个
for
循环的块,因此它是相同的
 i
被存储在
funcs
中的每个函数使用,并且
i
在该循环中以
10
结尾,因此
funcs
中的十个返回函数中的任何一个在调用时将始终评估为
10

本书的下一页指出,要解决上述问题,只需进行一行更改:只需将

i
设为块作用域即可,因此在
let i
中使用
var i
而不是
for
循环,如下:

function constfuncs() {
    let funcs = [];
    for(let i = 0; i < 10; i++) {
        funcs[i] = () => i;
    }
    return funcs;
}

let funcs = constfuncs();
funcs[5]() //is now 5, as desired

但是,我不明白这是如何解决问题的。对我来说,

i
似乎仍然只存在一次,但十个创建的函数中的每一个似乎都可以访问内存中
i
的自己的版本/副本。如果在
for
循环中声明了一个新的块范围局部变量,并在每次迭代时为其分配了
i
的值,然后使用
i
作为
funcs
的每个函数返回的值,则如果在每次迭代时都重新声明循环中声明的块范围局部变量(如使用新内存),也许这对我来说更有意义;但即便如此,
i
不是,但上面的内容仍然可以按预期工作。这引出了我的问题:

  1. JavaScript 中的闭包是如何在内存中管理的?我对 JS 和闭包相当陌生,并且习惯了具有堆栈和堆的语言。
  2. 定义闭包的行为是否会导致将内存从堆栈复制到堆以保存外部局部变量以供该闭包使用?这是因为闭包似乎违背了仅存储在堆栈上的局部变量,因为它仍然可以访问它们。
  3. 对于声明它们的外循环的每次迭代,是否会在内存中重新创建块作用域局部变量(每次在不同的内存位置)?
  4. 如果对 3 可能是肯定的,那么这是否受到是否使用闭包的影响(即,一般情况下,在循环迭代中仅在内存中创建一次,但在每次循环迭代中,如果闭包在范围内具有该局部块变量)为了它)?
  5. “绑定”是指内存中变量或常量的不同位置吗?我问,正如书中在对上述解决方案的解释中指出的那样,“因为
    let
    const
    是块作用域,循环的每次迭代都定义了一个独立于所有其他迭代的范围的范围,并且每个迭代都定义了一个范围。范围有自己独立的
    i
    绑定。”

问题 1-2 是相关的,问题 3-4 也是相关的;一切都与上面的例子有关。欢迎任何其他想法来解释为什么上面的第二种方法(带有

let i
)成功地修复了解决方案。

javascript memory-management closures heap-memory local-variables
1个回答
0
投票

从技术上讲,我不会回答你们任何问题,但我可以解释您想知道“如何解决问题”的部分:

在 for 循环初始化中使用

let
声明的变量 被视为在循环“主体” 中声明,根据定义。这是带有
let
的 for 循环的特殊行为。

另一方面,用

var
声明的变量与定义 for 循环的范围具有相同的范围(即在循环“主体”之外)。

示例

因此,如果您在 for 循环中声明

let i
outside,您将再次得到错误的值
10
(如您所料):

function constfuncs(){
    let funcs = [];
    let i;
    for( i = 0; i < 10; i++ ){
        funcs[ i ] = function(){
            return i;
        };
    }
    return funcs;
}

另一方面,即使您将

var
保留在循环初始化中,但在循环体中使用 let 声明一个
new 变量
,您也会得到所需的值
5
:

function constfuncs(){
    let funcs = [];
    for( var i = 0; i < 10; i++ ){
        let value = i;            // <-- new variable in every iteration
        funcs[ i ] = () => value;
    }
    return funcs;
}

备注

较新的

let
声明是专门为这种行为设计的,因为人们往往对
var
的行为感到困惑。

(我认为你想知道:IMO

let
有太多特殊情况。当你
思考它时,let使行为更加直观,但如果你想理解它,就会变得更复杂。但它仍然解决了
var
的重要缺陷。)

© www.soinside.com 2019 - 2024. All rights reserved.