问题 这个JavaScript闭包函数如何在没有全局变量的情况下重用一个对象?


我决定向前迈进一步,试图理解 使用Javascript 然后再读一遍 Javascript:好的部分。这是第一个疑问:

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

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分钟)

(如果有人想到更好的头衔,请酌情建议......)


5359
2017-08-09 06:00


起源

克罗克福德真的建议吗?哪一页?如果有决赛,这会更有意义 () 在你的代码的最后,意思是 digit_name 获取外部函数的返回值,这是内部函数。 - apsillers
我的错。忘了添加();最后。对不起大家。 - Nobita
“我想避免使用全局变量,因为它们是邪恶的,”  - 不,他们不是,比刀说更邪恶 - 这就是你如何使用它们的重要性。 (即便如此,不恰当地使用全局变量......不恰当 - 不是邪恶的。)但无论如何,你开始避免全局变量的代码会立即创建一个全局代码(digit_name)。 - nnnnnn
@nnnnnn:我其实是在引用D.Crockford,我听过他在两次会议上说:) - Nobita
是的,克罗克福德先生似乎看到了绝对的一切。我认为灰色地带让他紧张。 (并不是说他没有很多好的建议 - 问题在于弄清楚哪些建议只是他个人的偏好,而不是“事实”。) - nnnnnn


答案:


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

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 被称为,所以效率较低。

图形示例:

道格拉斯·克罗克福德(Douglas Crockford)提供的示例是一个非常艰难的例子,在开始学习闭包和范围链的工作原理时 - 很多东西都包含在少量代码中。我建议看一下闭包的视觉解释,比如这个: http://www.bennadel.com/blog/1482-A-Graphical-Explanation-Of-Javascript-Closures-In-A-jQuery-Context.htm


11
2017-08-09 06:06



“当你在JavaScript中声明函数内部的函数时,会创建一个闭包” - 当你 呼叫 在另一个函数中声明的函数 - 每个调用都有自己的闭包... - nnnnnn
调用在另一个函数内部声明的函数不会创建新的闭包。您将重新使用闭包(并且每个调用都有自己的私有范围),但不会创建新的。 - Elliot B.
道歉,你是对的。我的意思是每次你打电话给 含 创建新闭包的函数。我的意思是更多的澄清而不是更正 - 你可能会说内部的一个在调用包含它之前不会被声明,但是对于这个概念的新手,他们可能会查看代码并认为一切都已经被声明了。 。 - nnnnnn


这不是答案,而是在给定示例仍然令人困惑的情况下作出澄清。

首先,让我们澄清一下。 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 功能。基本上两个函数共享此数组。这基本上就是闭包:函数之间共享的变量。我喜欢把它想象成一种 私人的 全局变量 - 它感觉像全局变量,因为所有函数都有共享访问权限,但闭包之外的代码无法看到它。


3
2017-08-09 06:33





简单地说,第一个代码的问题是 它会在每次调用时创建一个数组 并从中返回一个值。由于您正在创建一个数组,这是一个开销 每次打电话

在第二个代码中,它创建一个声明的闭包 只有一个数组 并返回一个从该数组返回值的函数。基本上, digit_name 现在携带它自己的阵列,而不是每次通话。你的功能得到了 从现有的数组 从关闭。


另一方面,闭合,如果使用不当可以并且会吃掉记忆。闭包通常用于保护内部代码免受外部作用域的影响,并且通常在外部访问受限的情况下实现。

除非对它们的所有引用都是“无效的”,否则GC不会破坏对象。在闭包的情况下,如果你不能进入它们来杀死那些内部引用,那么这些对象将不会被GC破坏并永远会占用内存。


0
2017-08-09 06:17



实际上,当闭包中涉及的所有函数超出范围时,所有关联对象都会被垃圾收集。这适用于所有浏览器。唯一的例外是在IE中包含循环引用的DOM对象。 - slebetman