在Javascript中使用自执行函数来限定作用域

4
以下代码循环遍历6个输入按钮,并将onclick事件添加到每个按钮上,该事件会弹出相应迭代的索引号:
for (var i = 1; i < 6; ++i) {
    var but = document.getElementById('b_' + i);
    (function (el) {
        var num = i;
        but.onclick = function () {
            alert(num);
        };
    })(but);
}

如您所见,在每次迭代中有一个自我调用函数,它创建了一个作用域来存储该作用域中的迭代索引。
我一直使用这种模式来附加依赖于在迭代期间更改的变量的事件。
有人能够向我解释为什么上述内容有效,以及如何在该作用域中捕获num变量吗?
此外,上面使用的自我调用函数是否被称为闭包?

是的,那肯定是一个闭包。 - anddoutoi
那里肯定有一个闭包。请注意,https://dev59.com/6XVD5IYBdhLWcg3wBm5h 的一些答案也回答了你的问题,尽管另一个问题是不同的。 - outis
1
就我而言,我不会称呼那个匿名函数为“自调用”的,因为那意味着“递归”。更确切地说,它是“立即调用”之类的东西。 - xtofl
实际上,它确实会这样做,因为它在函数定义的末尾调用了自身:})(but); - Andreas Grech
1
@Skilldrick 为了防止语法错误 - Breton
显示剩余8条评论
5个回答

9

是的,这是一个闭包。

每次执行函数时,都会创建一个新对象来保存(作为其属性)使用var声明的变量和在其中声明的每个函数。此对象称为执行上下文(有时称为作用域对象)。

每次声明函数时(或在表达式中定义函数),新函数都会附加到当前的执行上下文对象。这就创建了所谓的作用域链。

当执行代码需要将标识符解析为值时,它首先查找当前执行上下文的属性。如果未找到标识符,则使用正在执行的函数附加的exection上下文对象。它沿着作用域链继续向上,直到达到全局级别为止。

在您的示例中,每次“自我调用函数”被执行时,都会创建一个新的执行上下文对象,其中包含属性elnum。由于分配给onclick的函数是在此执行上下文内创建的,因此每次都会获得此函数的新实例。这些实例将各自附有相应的执行上下文对象。因此,第一个将具有在num被分配为1时的执行上下文,第二个将具有在num被分配为2时的执行上下文,依此类推。

当每个onclick函数运行时,代码最初将在当前执行上下文中查找标识符num。但是此内部函数没有定义一个num,因此它找不到。因此,JavaScript会查找创建函数时附加的执行上下文。在这里,它将找到num,并且num将包含在该迭代期间分配给它的值,如上所述。


1

0
for (var i = 1; i < 6; ++i) {
    var but = document.getElementById('b_' + i);
    (function (el) {
        var num = i;
        but.onclick = function () {
            alert(num);
        };
    })(but);
}

现在让我们开始执行循环。
最初,i=1,但具有id ='b1'的domelement。
现在是调用函数的时候了。
好的,它使用but('b1')参数值调用内部函数(带有参数el)并开始执行它,这就是调用实际上意味着要立即执行它。
现在在里面
分配了num的新实例为1。
但.onclick被分配了一个函数,因此将函数存储在内存中,同时看到它访问num,因此num现在是封闭变量,这意味着它的生命周期增加,以便在调用时由onclick函数访问。

下一次迭代
现在i的值为2,但具有id ='b2'的domelement。
现在是调用函数的时候了。
它使用but(value='b2')的参数值调用内部函数(带有参数el)。
现在在里面
分配了num的新实例为2。
但.onclick被分配了一个函数,因此将函数存储在内存中,同时看到它访问num,因此num现在是封闭变量,这意味着它的生命周期增加,以便在调用时由onclick函数访问。
类似地,所有其他操作都会执行,直到循环终止。


0

这是否意味着在闭包之外定义的var i是一个全局变量,从而使自调用函数可以访问它?


只有在全局上下文中评估外部循环时,i 才是全局的。这是因为在定义函数时 i 在作用域内,使得 i 在函数中可见。另外,如果你对一个问题有疑问,请发表评论。回答是用来回答问题的,而你现在已经有足够的积分来留下评论了。 - outis
但是自调用函数的作用域如何在循环的每次迭代后保留? - Andreas Grech
@outis,抱歉,我显然还不习惯这种结构,但我会记住你说的话。 - Marco

0

每次循环中,评估 function (el) {...} 将创建一个新的匿名函数,该函数立即被调用。在匿名函数内部,创建一个名为num的变量(因为JS不使用静态变量,每次都是新的)。当调用函数时(立即调用),num被赋予i的值。这给了我们6个匿名函数和6个num,每个都包含1到6的值。

每次调用匿名函数时,都会创建一个内部匿名函数并将其存储为单击处理程序。内部函数引用一个num。因此,创建了一个闭包,确保当外部函数退出时,num不会被销毁。如果内部函数被丢弃,num也将很快跟随。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接