“闭包”的定义:

6
让我问一个问题。这是关于JavaScript中的闭包,但不是关于它们如何工作的。
David Flanagan在他的“JavaScript权威指南第6版”中写道:
技术上讲,所有JavaScript函数都是闭包:它们是对象,并且与它们相关联的有一个作用域链。
这是正确的吗?我可以称每个函数(函数对象+它的作用域)为“闭包”吗?
并且stacks标签“closures”说:
闭包是指从定义它的作用域中引用(关闭)变量的头等函数。如果闭包在其定义的范围之后仍然存在,则它关闭的变量也将继续存在。
在JavaScript中,每个函数都引用了其定义的作用域中的变量。因此,它仍然有效。
问题是:为什么有那么多开发人员持不同意见?这个理论有什么问题吗?不能用作一般定义吗?

是的,定义是正确的,请看这个答案:https://dev59.com/TXVD5IYBdhLWcg3wQZUg - frhack
函数是一种类型,闭包则是你使用它的方式。 - Derek 朕會功夫
函数不等于闭包。请注意声明函数和调用该函数多次之间的区别,声明函数会创建一个对象,即该函数,而调用该函数多次会创建多个闭包。 - nnnnnn
4个回答

2

从技术上讲,所有的函数都是闭包。但是,如果函数没有引用任何自由变量,闭包的环境就是空的。只有当有需要保存闭合变量和函数代码时,函数和闭包之间的区别才是有趣的。因此,通常将不访问任何自由变量的函数称为函数,而将访问这些变量的函数称为闭包,以便你了解这种区别。


“从技术上讲,所有函数都是闭包。”- 不,只有在调用(包含的)函数时才会创建闭包。一个被调用十次的函数将产生十个闭包。 - nnnnnn
不,以C语言为例,我们有一些不是闭包的函数。 - frhack
@frhack 我的回答仅适用于Javascript上下文,这不是一种语言无关的问题或答案。 - Barmar
@nnnnnn 只有在包含函数被调用时,该函数本身才会被创建。当这种情况发生时,该函数被创建,并且它是一个闭包。 - Barmar
但是闭包适用于包含函数的调用,不是吗?因为如果包含函数有三个内部函数,那么它们都可以访问包含函数的变量,所以是一个闭包针对包含函数,还是三个闭包呢? - nnnnnn
@nnnnnn 包含函数是一个闭包,而内部函数则是另外三个闭包。 - Barmar

1
这是一个难以确定的术语。仅仅“声明”了的函数只是一个函数。调用函数才会形成闭包。通过调用函数,为传递的参数和已声明的局部变量分配空间。
如果一个函数仅返回某个值,并且该值很简单(比如,什么都没有,或者只是一个数字或字符串),那么闭包就消失了,它并不特别有趣。然而,如果一些参数或局部变量的引用(大多数情况下是相同的)“逃离”函数,则闭包——即为局部变量分配的空间以及父级空间链——会保留下来。
以下是一种可能从函数中“逃逸”的引用方式:
function escape(x, y) {
  return {
    x: x,
    y: y,
    sum: function() { return x + y; }
  };
}

var foo = escape(10, 20);
alert(foo.sum()); // 30

从函数返回并保存在“foo”中的对象将保留对这两个参数的引用。以下是一个更有趣的示例:

function counter(start, increment) {
  var current = start;
  return function() {
    var returnValue = current;
    current += increment;
    return returnValue;
  };
}

var evenNumbers = counter(0, 2);
alert(evenNumbers()); // 0
alert(evenNumbers()); // 2
alert(evenNumbers()); // 4

在这个例子中,返回的值本身就是一个函数。该函数涉及到对参数“increment”和局部变量“current”的引用。

我认为将闭包的概念与函数作为一等对象的概念混淆起来有些问题。虽然它们之间存在协同作用,但它们确实是两个不同的概念。

需要说明的是,我并不是一个形式主义者,而且我对术语非常不熟悉,所以这篇文章可能会被人们批评。


那么,无论如何,带有其作用域的函数不能被定义为“闭包”吗?这个函数需要返回另一个函数才能“有趣”吗?Flanagan 对每个函数都有所说。所以,我很困惑。 - Ivan
1
@Ivan 就像我说的,我对定义非常糟糕。对我来说,一个没有被调用的函数并不是真正的闭包,因为什么都没有发生:没有分配内存,没有初始化,没有返回值,没有副作用等等。只有调用函数才会导致这些事情(可能)发生。 - Pointy
一个超级简单的函数,只是返回一个标量技术上创建了一个闭包,但由于它消失了,就像量子物质-反物质对一样突然出现。它们存在,但除了理论上没有什么意义。 - Pointy
1
我不太确定你所说的“think otherwise”是什么意思。你还在与哪个定义进行斗争?除了JavaScript之外,“closure”还可以指什么?这是一个不特定于JavaScript的术语。 - Pointy
这样更好。这也是目前为止唯一一个对已声明的函数和实际调用的函数进行了重要区分的答案。 - nnnnnn
显示剩余5条评论

1

我会尝试回答你的问题,知道你在面试中被问到了什么是闭包(从上面的评论中读到的)。

首先,我认为你应该更具体地说明“想法不同”是如何表现的。

也许我们可以谈论一下这个noop函数的闭包:

function() {}

但是似乎没有变量限制作用域,所以这并没有意义。

我认为即使考虑这个例子也不是很好:

function closureDemo() {
    var localVar = true;
}
closureDemo();

由于在此函数调用之后无法访问其变量,因此JavaScript和C语言之间没有区别。

再次强调,既然您说您在面试中询问了闭包是什么,那么最好先展示一个例子,在closureDemo()调用后通过外部函数访问一些局部变量的方式。例如:

function closureDemo() {
    var localVar = true;
    window.externalFunc = function() {
        localVar = !localVar;  // this local variable is still alive
        console.log(localVar); // despite function has been already run,
                               // that is it was closed over the scope
    }
}
closureDemo();
externalFunc();
externalFunc();

然后评论其他案例,推导出最常见的定义,因为这样更有可能让面试官同意你的观点,而不是引用Flanagan并立即尝试找到你读过的页面作为更好的证明。也许你的面试官只是认为你实际上不知道闭包是什么,只是从书上读了定义。无论如何,祝你下次好运。

指示不清楚。最后告诉人们“地球”不是行星,而是我们居住的地方。 :D 我怎么知道何时从全局定义开始并进入“有趣的情况”,否则呢?看起来我的定义是正确的,这只是解释。 我可以按照你说的做。但是...这是否意味着面试官只是不知道更抽象的定义,可能也不知道“函数与其定义的范围一起工作,而不是在调用它的范围内工作”的概念? - Ivan
也许你的面试官只是认为你并不了解闭包是什么,而只是从书上读到了定义。那么,现在阅读书籍就失败了吗?无论如何都应该不会。与“闭包是这样的…看我举的例子”相比,它更像是高级知识。如果他确实知道这个定义并怀疑我的知识水平,他会问一些更具体的问题或要求示例,或者给我一些练习来解决。我仍然相信他并不知道它。 - Ivan
@Ivan 可能他没有这样做,可能他非常不耐烦听解释。在这种情况下,由于团队领导/经理的故意行为,拒绝他们的提议是一个很好的理由。否则,用代码片段证明这些定义并不太困难。 - ivkremer

0

定义是正确的。 闭包保持了它诞生的作用域。

考虑这个简单的代码:

getLabelPrinter = function( label) {
  var labelPrinter = function( value) {
      document.write(label+": "+value);
  }
  return labelPrinter; // this returns a function
}

priceLabelPrinter = getLabelPrinter('The price is');
quantityLabelPrinter = getLabelPrinter('The quantity is');

priceLabelPrinter(100);

quantityLabelPrinter(200);

//output:
//The price is: 100 The quantity is: 200  

https://jsfiddle.net/twqgeyuq/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接