我决定向前迈出一步,试图理解 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
时,为什么第一行被“忽略”?我错过了什么?这里真的发生了什么?
我的这个例子并不只是在书中,而是基于video(第26分钟)
(如果有人想到更好的头衔,请酌情建议......)
答案 0 :(得分:11)
我确定你打算让你的第二个例子函数立即执行(即自我调用)函数,如下所示:
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
答案 1 :(得分: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
函数中使用。基本上两个函数共享此数组。这基本上就是闭包:函数之间共享的变量。我喜欢把它想象成一种私有全局变量 - 它感觉像全局变量,因为所有函数都有共享访问权限,但是闭包之外的代码看不到它。
答案 2 :(得分:0)
简单地说,第一个代码的问题是它在每次调用时创建一个数组并从中返回一个值。这是一个开销,因为每次调用时都会创建一个数组。
在第二个代码中,它创建一个闭包,声明只有一个数组并返回一个从该数组返回值的函数。基本上,digit_name
现在带有它自己的数组,而不是每次调用一个。您的函数从闭包中的现有数组中获取。
另一方面,如果使用不当,关闭可能会占用内存。闭包通常用于保护内部代码免受外部范围的影响,并且通常在外部访问受限的情况下实现。
除非对它们的所有引用都是“无效的”,否则GC不会破坏对象。在闭包的情况下,如果你不能进入它们来杀死那些内部引用,那么这些对象就不会被GC破坏,永远会占用内存。