【问题标题】:How does this JavaScript closure-function reuse an object without a global variable?这个 JavaScript 闭包函数如何重用没有全局变量的对象?
【发布时间】:2012-08-09 06:00:33
【问题描述】:

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

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

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

D.Crockford 声称这是,因为每次调用该函数时,都会完成names 的新实例化。所以,然后他通过这样做转移到 closure 解决方案:

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

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

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

我不仅基于书中的示例,而且基于video(第 26 分钟)

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

【问题讨论】:

  • 克罗克福德真的建议这样做吗?哪一页?如果在代码末尾有一个最终的(),这将更有意义,这意味着digit_name 获取外部函数的返回值,即内部函数。
  • 我的错误。忘记加();在末尾。对不起,伙计们。
  • “我想避免使用全局变量,因为它们是邪恶的,” - 不,它们不是,就像刀是邪恶的一样 - 这就是你的方式使用它们很重要。 (即便如此,不恰当地使用全局变量是......不恰当的 - 不是邪恶的。)。但在任何情况下,为避免全局变量而开始使用的代码都会立即创建一个全局变量 (digit_name)。
  • @nnnnnn:我实际上是在引用 D.Crockford,我在两次会议上都听到他这么说 :)
  • 是的,克罗克福德先生似乎对一切都视若无睹。我认为灰色地带让他紧张。 (并不是说他没有很多好的建议——问题是要弄清楚哪些建议只是他个人的偏好,表现为“事实”。)

标签: javascript closures


【解决方案1】:

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

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

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

图形示例:

您从 Douglas Crockford 提供的示例是一个非常困难的示例,当您学习闭包和作用域链的工作原理时,需要从一个非常困难的示例开始 - 大量的东西被打包到少量的代码中。我建议看一下对闭包的直观解释,例如:http://www.bennadel.com/blog/1482-A-Graphical-Explanation-Of-Javascript-Closures-In-A-jQuery-Context.htm

【讨论】:

  • “当你在 JavaScript 的函数内部声明一个函数时,会创建一个闭包” - 当你调用一个在内部声明的函数时另一个函数 - 每个调用都有自己的闭包...
  • 调用在另一个函数内部声明的函数不会创建新的闭包。您将重新使用闭包(并且每个调用都有自己的私有范围),但不会创建新的。
  • 我很抱歉,你是对的。我的意思是每次调用 包含 函数时都会创建一个新的闭包。我的意思是更多的澄清而不是更正 - 你可能会说在调用包含的内容之前不会声明内部的内容,但是对于这个概念的新手来说,他们可能会查看代码并认为所有内容都已经声明了.. .
【解决方案2】:

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

首先,让我们澄清一下。 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 函数中仍然可用。基本上这两个函数共享这个数组。这基本上就是闭包的含义:函数之间共享的变量。我喜欢将其视为一种 private 全局变量 - 感觉就像全局变量,因为所有函数都可以共享访问它,但闭包之外的代码看不到它。

【讨论】:

    【解决方案3】:

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

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


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

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

    【讨论】:

    • 实际上,当闭包中涉及的所有函数都超出范围时,所有关联的对象都会被垃圾回收。这适用于所有浏览器。唯一的例外是在 IE 中包含循环引用的 DOM 对象。
    猜你喜欢
    • 2014-06-24
    • 2011-10-20
    • 2023-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多