有效的javascript书有理解项目13(Iffy)

时间:2014-10-24 13:59:23

标签: javascript closures

试着阅读这本有趣的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通过引用而不是值来存储它们的外部变量。

老实说,我认为我理解通过引用存储信息而不是按值存储信息,但老实说,我不能将它们与上面的段落联系起来。 有人可以用更容易的术语向我解释这个或者指向我这样做的文章吗?我找不到任何容易理解的东西。

感谢您提前的时间!!

2 个答案:

答案 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]; }

因此,您在此代码中所做的是在循环中向数组添加新函数。如果您尝试调用该函数,ai将是未定义的,这就是您收到未定义错误的原因。

为了达到预期的效果,代码如下所示:

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)

这是一个非常常见的错误。让我为你简化一下。

以下程序应提醒123

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更好,原因有两个:

  1. 它更清晰,更易于阅读和理解。
  2. 它更快,因为没有调用任何函数。函数调用很慢。
  3. 希望有所帮助。