这个javascript闭包的解释是否正确?

时间:2012-12-26 02:04:53

标签: javascript closures

对于tl; dr,当为表中的行创建单击处理程序时,使用闭包使得该函数指向内存中的相同位置,而不是每次都使用下面的代码。

否则:

我正在学习javascript,我想我理解闭包是什么以及为什么它有用。这是我的推理;这是对的吗?

对于HTML表格,下面的代码始终显示最后一行被点击,即使我点击第一行或第二行。我的理由是代码创建了3个不同的堆栈帧,每个堆栈帧用于i等于0,1和2.由于2是最近的堆栈帧,因此fnPtr的返回值指向2。

var table = document.getElementById("tableid3");
var rows = table.getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
    var curRow = table.rows[i];
    //get cell data from first col of row
    var cell = curRow.getElementsByTagName("td")[0]; 
    var fnPtr = function() {
        alert("row " + i + " data="+ cell.innerHTML);
    }
curRow.onclick = fnPtr;
}

现在,下面的代码(来自SO问题)使用闭包并只创建一个堆栈帧,即createfunc()的框架。但是在这个框架内部是一个局部变量tmp,它指向匿名函数。当使用0,1和2调用createfunc()时,返回值指向相同的堆栈帧(createfunc()的堆栈帧内的堆栈帧),这意味着不会创建3个堆栈帧。当createfunc()每次返回时,返回值槽中的值指向内存中的相同位置,而不是每次都与上面的代码不同。

function createfunc(i) {
    var tmp = function() { console.log("My value: " + i); };
    console.log(tmp);
    return tmp;
}
for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

我了解闭包是如何工作的吗?

2 个答案:

答案 0 :(得分:1)

这是关于范围,而不是堆栈。每个函数定义都在JavaScript中创建一个新的作用域,函数总是可以访问它们的祖先作用域。

因此,在您的第一个示例中,fnPtr的所有实例都可以从父作用域访问相同的i。在点击任何行时,for循环已经完成,i将为3。

在第二个示例中,每个返回的tmp函数都可以访问传递给工厂函数的i参数,该参数在每次迭代/调用时都会有所不同。

答案 1 :(得分:0)

我不确定考虑javascript在堆栈帧方面的工作方式是个好主意 - 对于那个javascript来说太高了。

在第一个代码片段中,您创建的fnPtr函数对象捕获实际变量(但还不是它的值),因此如果该变量稍后更改,则在调用函数时,i变量使用当前时间的值。你的循环可能已经创建了100个fnPtr函数对象,但是它们中的每一个都拥有对完全相同的i变量的引用。

第二个片段利用了这样一个事实:当您将原始值(例如,数字)作为函数参数传递给函数时,javascript 会创建它的副本。因此,每次调用createfunc时,都会生成一个新的变量副本,并将此新变量的值设置为参数在调用createfunc时的值。我强调的是这是一个新变量,没有其他函数可以引用它。因此,当您的createfunc返回一个新函数时,该新函数将保存对已复制的新的独占i变量的引用。