我决定向前迈进一步,试图理解 使用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分钟)
(如果有人想到更好的头衔,请酌情建议......)
我确定你的意思是让你的第二个例子函数立即执行(即自我调用)函数,如下所示:
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
这不是答案,而是在给定示例仍然令人困惑的情况下作出澄清。
首先,让我们澄清一下。 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
功能。基本上两个函数共享此数组。这基本上就是闭包:函数之间共享的变量。我喜欢把它想象成一种 私人的 全局变量 - 它感觉像全局变量,因为所有函数都有共享访问权限,但闭包之外的代码无法看到它。
简单地说,第一个代码的问题是 它会在每次调用时创建一个数组 并从中返回一个值。由于您正在创建一个数组,这是一个开销 每次打电话。
在第二个代码中,它创建一个声明的闭包 只有一个数组 并返回一个从该数组返回值的函数。基本上, digit_name
现在携带它自己的阵列,而不是每次通话。你的功能得到了 从现有的数组 从关闭。
另一方面,闭合,如果使用不当可以并且会吃掉记忆。闭包通常用于保护内部代码免受外部作用域的影响,并且通常在外部访问受限的情况下实现。
除非对它们的所有引用都是“无效的”,否则GC不会破坏对象。在闭包的情况下,如果你不能进入它们来杀死那些内部引用,那么这些对象将不会被GC破坏并永远会占用内存。