1

对于 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
}

我了解闭包的工作原理吗?

4

2 回答 2

1

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

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

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

于 2012-12-26T02:21:56.750 回答
0

我不确定考虑 javascript 在堆栈帧方面的工作方式是一个好主意 - javascript 的级别太高了。

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

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

于 2012-12-26T02:19:21.620 回答