12

我决定在尝试理解Javascript并再次阅读Javascript: The Good Parts方面向前迈出一步。这是第一个疑问:

假设我想避免使用全局变量,因为它们是邪恶的,所以我有以下内容:

var digit_name = function(n) {
 var names = ['zero','one','two','three'];
 return names[n];
}

D.Crockford 声称这很,因为每次调用该函数时,names都会完成一个新的实例化。因此,然后他通过执行以下操作转移到闭包解决方案:

var digit_name = function () {
  var names = ['zero', 'one', 'two', 'three'];
  return function (n) {
    return names[n];
  }
}();

这使得names变量存储在内存中,因此每次调用时它都不会被实例化digit_name

我想知道为什么?当我们打电话时digit_name,为什么第一行被“忽略”?我错过了什么?这里到底发生了什么?

我不仅在书中,而且在这个视频(第 26 分钟)中建立了这个例子

(如果有人认为更好的标题,请酌情提出......)

4

3 回答 3

12

我确定您的意思是让您的第二个示例函数成为立即执行(即自调用)函数,如下所示:

var digit_name = (function () {
  var names = ['zero', 'one', 'two', 'three'];
  return function (n) {
    return names[n];
  }
})();

区别涉及带有闭包的范围链。JavaScript 中的函数具有范围,因为它们将在父函数中查找未在函数本身中声明的变量。

当您在 JavaScript 中的函数内部声明一个函数时,就会创建一个闭包。闭包描述了一个范围级别。

在第二个示例中,digit_name设置为等于自调用函数。该自调用函数声明names数组并返回一个匿名函数。

digit_name因此变成:

function (n) {
  //'names' is available inside this function because 'names' is 
  //declared outside of this function, one level up the scope chain
  return names[n];
}

从您的原始示例中,您可以看到它names是从返回的匿名函数(现在是digit_name)在作用域链的上一级声明的。当该匿名函数需要names时,它会沿着作用域链向上移动,直到找到声明的变量——在这种情况下,names它位于作用域链的上一层。

关于效率:

第二个例子效率更高,因为names只声明一次——当自调用函数触发时(即 var digit_name = (function() { ... })(); )。当digit_names被调用时,它会查找作用域链,直到找到names.

在您的第一个示例中,names每次digit_names都调用 get 声明,因此效率较低。

图形示例:

您从 Douglas Crockford 提供的示例是一个非常困难的示例,在学习闭包和作用域链如何工作时开始学习 - 大量内容打包到少量代码中。我建议看一下闭包的视觉解释,比如这个:http ://www.bennadel.com/blog/1482-A-Graphical-Explanation-Of-Javascript-Closures-In-A-jQuery-上下文.htm

于 2012-08-09T06:06:37.823 回答
3

这不是一个答案,而是一个澄清,以防给定的示例仍然令人困惑。

首先,让我们澄清一下。digit_name不是您在代码中看到的第一个函数。该函数只是为了返回另一个函数而创建的(是的,您可以像返回数字或字符串或对象一样返回函数,实际上函数是对象):

var digit_name = (
    function () { // <------------------- digit name is not this function

        var names = ['zero', 'one', 'two', 'three'];

        return function (n) { // <------- digit name is really this function
            return names[n];
        }
    }
)();

为了简化示例并说明闭包概念,而不是将其与自调用函数(您可能还不熟悉)之类的东西混为一谈,您可以像这样重新编写代码:

function digit_name_maker () {
    var names = ['zero', 'one', 'two', 'three'];

    return function (n) {
        return names[n];
    }
}

var digit_name = digit_name_maker(); // digit_name is now a function

您应该注意的是,即使在函数names中定义了数组,digit_name_maker它仍然在digit_name函数中可用。基本上这两个函数共享这个数组。这基本上就是闭包的含义:函数之间共享的变量。我喜欢将其视为一种私有全局变量——感觉就像全局变量,因为所有函数都可以共享访问它,但闭包之外的代码看不到它。

于 2012-08-09T06:33:29.950 回答
0

简而言之,第一个代码的问题在于它在每次调用时创建一个数组并从中返回一个值。这是一个开销,因为您每次调用时都在创建一个数组。

在第二个代码中,它创建了一个闭包,它只声明一个数组并返回一个从该数组返回值的函数。基本上,digit_name现在携带它自己的数组,而不是每次调用都进行一次。您的函数从闭包的现有数组中获取。


另一方面,如果使用不当,闭包会消耗内存。闭包通常用于保护内部代码免受外部范围的影响,并且通常在外部访问受限的情况下实现。

对象不会被 GC 销毁,除非对它们的所有引用都“无效”。在闭包的情况下,如果你不能进入它们来杀死那些内部引用,那么对象将不会被 GC 销毁并且永远会吃掉内存。

于 2012-08-09T06:17:24.480 回答