试着阅读这本有趣的javascript精彩书(但我还是新手)..
在第13项中,它讨论了立即调用的函数表达式以创建局部范围。
不幸的是,我不能在示例
下面绕过我的脑袋function wrapElements(a) {
var result = [], i, n;
for ( i = 0, n = a.length; i < n; i++ ) {
result[i] = function() { return a[i]; };
}
return result;
}
var wrapped = wrapElements([10,20,30,40,50]);
var f = wrapped[0];
f(); // undefined....
我真的尝试多次阅读该页面,但我仍然不理解
特别是在本书的陈述之下。
程序中的错误来自于程序员显然希望在创建嵌套函数时存储i值的函数。但事实上,它包含对i的引用。由于在创建每个函数后i的值发生变化,因此内部函数最终会看到i的最终值。这是关闭的关键点。 Closures通过引用而不是值来存储它们的外部变量。
老实说,我认为我理解通过引用存储信息而不是按值存储信息,但老实说,我不能将它们与上面的段落联系起来。 有人可以用更容易的术语向我解释这个或者指向我这样做的文章吗?我找不到任何容易理解的东西。
感谢您提前的时间!!
答案 0 :(得分:2)
让我看看我是否可以帮助你理解一点。立即调用函数表达式的概念或(我学习它们时的流行术语)自我执行匿名函数可能很难掌握,我建议您在真正深入研究之前掌握JavaScript知识,但我可能会能够以一种有助于你理解的方式向你解释。
所以 - 让我们从检查可以通过以下两种方式之一完成的正常函数声明开始:
function someFunction (param1, param2) {
//do stuff
};
OR
var someFunction = function (param1, param2) {
//do stuff
};
要调用此函数,您可以这样调用它:
someFunction("param1value", "param2value");
这很好,并且完全符合它的预期。但是如果你需要一个在它运行时立即执行或调用的函数,并且不想将对象添加到全局命名空间呢?这是IIFE(SEAF)的真正好处。这是匿名函数的基本结构:
(function () {
})();
(function () {
之后的第一组括号将参数传递到函数的范围内。第二组括号})();
用于调用函数并将参数传递给函数。在括号中包装函数声明使函数匿名并允许它立即执行或调用。
让我们采用一个非常基本的开始框架示例,并进行更详细的解释:
(function (window, undefined) {
})(window);
我记得当我第一次看到这个时,我无法弄清楚这里发生了什么以及重点是什么......这个函数接受两个参数传递到函数的范围,一个窗口对象和一个未定义的对象。 (function (window, undefined) {
然后当我们调用它时,我们只传入一个窗口对象(全局窗口范围)。 })(window);
为了帮助您理解,这就像编写一个函数并执行它一样:
function doStuff (window, undefined) {
//do stuff here
};
doStuff(window);
那么为什么不让人们以这种方式编写代码而不是担心这些IIFE?好吧,以这种方式编写函数可能会阻塞您的全局范围,这意味着现在您已经定义了一个doStuff()
对象,该对象可以在整个项目的范围内使用。如果你有一个非常大的项目或框架,你通常只想将一个对象暴露给全局范围,并保持其他所有内容匿名,这样它就不会覆盖或被覆盖在应用程序中的其他代码覆盖
这真的是基础知识,但为了帮助您更多地理解语法,让我为您做一个真正的基本工作示例,只是为了帮助您了解这个概念。在这个例子中,一旦代码运行,我们只是将两个数字相乘,无论你传入函数的两个数字。然后我们将结果输出到一个带有id&#34;结果&#34;的文本框。你可以在这里玩:http://jsfiddle.net/k7f4n0mk/
(function (number1, number2) {
document.getElementById("result").value = (number1 * number2);
})(5, 10);
如果我们在没有IIFE的情况下写这个,你首先必须定义函数,然后调用它,它看起来像这样:
function multiply(number1, number2) {
document.getElementById("result").value = (number1 * number2);
};
multiply(5, 10);
您可以在此处查看此示例:http://jsfiddle.net/k7f4n0mk/1/
这两个例子都会产生完全相同的结果,我假设你对第二个结果很满意,因为它是该语言的基础知识之一,所以你为什么要担心关于这个写一个函数的全新方法,如果你以前的方式工作得很好吗?好吧,这又回到了保持全球范围清洁和保护本地范围。
每个人都非常熟悉jQuery javascript库。 jQuery的整个上下文包含在IIFE中,只有jQuery和别名$ object暴露给全局范围 - 这对于jQuery所做的一切都非常令人印象深刻。好吧,如果jQuery没有这个IIFE语法,那么他们声明的每个函数都可以在全局范围内使用,很容易被不知情的用户覆盖。它们可以覆盖jQuery使用的任何函数并完全破坏库 - 因此我们希望保护jQuery使用的所有函数(本地作用域)并通过仅暴露必要的对象(jQuery和$)来保持全局作用域的清晰。
我知道这是一个非常长的答案,但我希望我已经能够帮助你对这个问题有更多的了解。如果您有任何其他问题,请与我们联系。
<强> - 编辑 - 强>
至于你的问题 - 让我看看我是否可以帮助更详细地解释。
以下是您正在使用的代码:
function wrapElements(a) {
var result = [], i, n;
for (i = 0, n = a.length; i < n; i++) {
result[i] = function () { return a[i]; };
}
return result;
}
var wrapped = wrapElements([10, 20, 30, 40, 50]);
var f = wrapped[0];
f();
现在,当您致电var wrapped = wrapElements([10, 20, 30, 40, 50]);
wrapped现在引用了一系列函数,因为这是你在for循环中返回的内容:
wrapped = [function () { return a[i]; },function () { return a[i]; },function () { return a[i]; },function () { return a[i]; },function () { return a[i]; }]
然后,当你调用var f = wrapped[0]
时,f成为对数组中第一个函数的引用
f = function () { return a[i]; }
因此,您在此代码中所做的是在循环中向数组添加新函数。如果您尝试调用该函数,a
和i
将是未定义的,这就是您收到未定义错误的原因。
为了达到预期的效果,代码如下所示:
function wrapElements(a) {
var result = [], i, n;
for (i = 0, n = a.length; i < n; i++) {
result[i] = a[i];
}
return result;
}
var wrapped = wrapElements([10, 20, 30, 40, 50]);
var f = wrapped[0];
我希望这有助于您理解更多。如果您需要任何进一步的帮助,请告诉我。
答案 1 :(得分:1)
这是一个非常常见的错误。让我为你简化一下。
以下程序应提醒1
,2
和3
:
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
alert(i);
}, 10);
}
但是,正如您所看到的那样,警告4
3次。发生的事情是,当调用setTimeout
的函数时,i
的值已经改变。这就是你面临的同样问题。
为了解决这个问题,我们使用了立即调用的函数表达式(IIFE)。
for (var i = 1; i <=3; i++) {
(function (new_i) {
setTimeout(function () {
alert(new_i);
}, 10);
}(i));
}
通过使用IIFE,我们创建了一个名为i
的新变量,其值为旧i
的值。现在,当旧的i
发生变化时,新的i
仍然保持不变。因此我们得到了预期的结果。
更好的方法是使用with
语句创建新的i
,如下所示:
for (var i = 1; i <= 3; i++) {
with ({ new_i: i }) {
setTimeout(function () {
alert(new_i);
}, 10);
}
}
使用with
比IIFE更好,原因有两个:
希望有所帮助。