需要用函数返回值才能称为闭包吗?

5

嘿,我在YouTube上看到了这个视频:http://www.youtube.com/watch?v=KRm-h6vcpxs

该视频主要讲解了IIFEs和闭包。但是我不明白的是,我是否需要返回一个函数才能称之为闭包。

例如:

function a() {
    var i = 10;
    function b() {
       alert(i);
    }
}

在这种情况下,我可以称之为闭包吗?因为它从外部函数作用域访问了变量'i',或者我需要像这样返回函数。
return function b(){alert(i);}

对@dystroy的回答点赞。你不需要在函数内部调用另一个函数来关闭第一个函数。这就像使用return null的人一样。你不必返回任何东西来完成函数。只需关闭它即可 :P - DaGLiMiOuX
这就是我想的,但在视频中给出了那样的定义,所以我想确认一下。 - Nav
1
@DaGLiMiOuX - OP正在询问closures,而不是讨论如何完成一个函数。 - nnnnnn
1
@Nav - 正如jcoder所说,对于闭包对程序员来说是有用的,你需要一个对内部函数的引用,以便在包含函数执行完成后的某个时刻调用它。请注意,有一些方法可以获得这样的引用而不返回内部函数,例如将其分配为事件处理程序或将其作为回调传递给其他函数。 - nnnnnn
@nnnnnn 哇!犯了个大错误,想说的是“结束函数不需要返回任何东西”,哈哈我在吃东西,没有太注意我的评论:P - DaGLiMiOuX
显示剩余5条评论
5个回答

9
一个闭包简单来说就是一个函数,它保存着它的词法环境,并且不会在自身死亡之前释放它。
可以将闭包想象成财迷的 Uncle Scrooge: enter image description here Uncle Scrooge 是一个守财奴。他绝不会放手他的钱。
同样地,闭包也是一个守财奴。它不会在自身死亡之前释放它的变量。
例如:
function getCounter() {
    var count = 0;

    return function counter() {
        return ++count;
    };
}

var counter = getCounter();

看到那个counter函数了吗?是由getCounter函数返回的吗?这个函数像守财奴一样,即使count变量属于getCounter函数调用,并且该函数调用已经结束,它也不会放开count变量。因此,我们称counter为闭包。
注意,每次函数调用都可能创建变量。例如,对getCounter函数的调用会创建一个名为count的变量。现在,当getCounter函数结束时,这个变量count通常会消失。
但是counter函数(可以访问count变量)不允许它在调用getCounter结束时消失。这是因为counter函数需要count。因此,它只有在自己死亡后才允许count死亡。
现在真正有趣的事情是,counter甚至是在调用getCounter内部产生的。因此,即使调用getCounter结束,counter也不应该存在。但是它仍然存在,即使在调用getCounter结束后,因为它逃离了getCounter的作用域(生存期)。 counter可以以许多方式逃离getCounter的作用域。最常见的方法是getCounter简单地返回counter。但是还有许多其他方法。例如:
var counter;

function setCounter() {
    var count = 0;

    counter = function counter() {
        return ++count;
    };
}

setCounter();

这里的getCounter的姊妹函数(得体地称为setCounter)将一个新的counter函数赋值给全局变量counter。因此,内部的counter函数逃离了setCounter的作用域并成为了一个闭包。
实际上,在JavaScript中,每个函数都是一个闭包。但是,我们在处理那些从父函数的作用域中逃脱出去,并保持某些属于父函数的变量在调用父函数后仍然存活的函数时才会意识到这一点。
更多信息请阅读这个答案:https://dev59.com/UWcs5IYBdhLWcg3wWClB#12931785

3
+1 如果您能在帖子中加入图片,会让它更有趣。不开心的人 - 请离开! :) - Jimbo
@Jimbo - 谢谢。至于那个给我投反对票的人-如果你正在阅读这篇文章,我可以知道为什么你认为我的回答值得一个负面评价吗? - Aadit M Shah

4

返回函数不会改变任何东西,重要的是创建它并调用它。这就产生了闭包,即从内部函数到创建它的作用域的链接(您可以将其视为指针)。它具有防止外部范围被垃圾回收的相同效果。


但在 OP 的例子中,b() 实际上并没有被调用,如果它从 a() 中被调用了,当 a() 完成后整个东西都可以被垃圾回收,因为在那个作用域中没有任何变量的持久引用。闭包只有在开始传递函数引用时才真正成为程序员关注的问题(无论以哪种方式)。 - nnnnnn
@nnnnnnn我认为这并不一定正确,因为我想直接从函数b()中打印或附加值到日志中,我不觉得返回该函数有任何意义...你怎么看??? - Nav

1
通过闭包的定义,函数与其所在的作用域之间的链接已经足够。因此,在JavaScript中创建函数就是创建了一个闭包,因为这是链接创建的地方 :-)
然而,为了利用这个特性,我们需要从与其定义不同的作用域中调用该函数——这就是实践中“使用闭包”一词所指的。这可以是较低或较高的作用域,函数不一定需要从定义它的函数中返回。
以下是一些示例:
var x = null;

function a() {
    var i = "from a";
    function b() {
        alert(i); // reference to variable from a's scope
    }
    function c() {
        var i = "c";
        // use from lower scope
        b(); // "from a" - not "c"
    }
    c();

    // export by argument passing
    [0].forEach(b); // "from a";
    // export by assigning to variable in higher scope
    x = b;
    // export by returning
    return b;
}
var y = a();
x(); // "from a"
y(); // "from a"

1
实际上,闭包是变量的容器,因此函数可以使用在其创建的作用域中的变量。

返回一个函数是在不同的作用域中使用它的一种方式,但更常见的用法是当它是异步调用的回调函数时。

任何情况下,函数从一个作用域中使用变量,并且该函数在不同的作用域中使用,都会使用闭包。例如:

var globalF; // a global variable

function x() { // just to have a local scope

  var local; // a local variable in the scope

  var f = function(){
    alert(local); // use the variable from the scope
  };

  globalF = f; // copy a reference to the function to the global variable

}

x(); // create the function
globalF(); // call the function

(这只是一个闭包的演示,让函数设置一个全局变量然后使用它并不是编写实际代码的好方法。)

1
以下是闭包的一些解释。对我来说,“tiger book”中的那个最能令我满意……比喻也有很大帮助,但只有在遇到这一个之后才有用……
- 闭包:在集合论中,闭包是一个(最小的)集合,在该集合上执行某些操作的结果也属于该集合,因此它是“在某些操作下最小的封闭社会”。
a) SICP:在抽象代数中,如果将操作应用于集合中的元素产生的结果仍然是集合中的元素,则称元素集合在该操作下是封闭的。Lisp 社区也不幸地使用“闭包”一词来描述一个完全不相关的概念:闭包是一种表示具有自由变量的过程的实现技术。
b) 维基百科:闭包是一个捕获其定义环境中自由变量的词法绑定的一等函数。一旦捕获了词法绑定,该函数就成为闭包,因为它“闭合”了那些变量。
c) 老虎书:堆上的数据结构(而不是栈上),其中包含函数指针(MC)和环境指针(EP),表示函数变量;

d) 在Lisp中,函数和一组变量绑定的组合被称为闭包;闭包是具有本地状态的函数;

e) Google I/O视频:类的实例类似于封装代码(vtab)的数据(instance obj),而在闭包的情况下,代码(函数变量)封装了数据。

f) 封装的数据对函数变量是私有的,这意味着闭包可用于数据隐藏。

g) 非函数式编程语言中的闭包:在C中使用带cookie的回调是一个类似的结构,还有glib“closure”:glib闭包是封装类似事物的数据结构:信号回调指针、cookie私有数据和闭包的析构函数(因为C中没有GC)。

h) 老虎书: "高阶函数"和"嵌套函数作用域"需要解决这样一个问题:父函数返回引用其父作用域中的变量的子函数,这意味着即使父级返回作用域中的变量也不能从堆栈中弹出...解决方案是在堆中分配闭包。

i) Greg Michaelson ($10.15):(在lisp实现中)闭包是一种标识自由变量和词法绑定变量之间关系的方式,当需要将自由变量冻结为定义范围中的值并返回函数值时(通常需要)。

j) 历史和词源学:Peter J. Landin于1964年定义了闭包这个术语,作为他的SECD机器用于评估表达式的环境部分和控制部分。 Joel Moses归功于Landin引入闭包这个术语,以指代一个λ表达式,其开放绑定(自由变量)已经通过词法环境关闭(或绑定),从而形成一个封闭的表达式或闭包。这种用法随后被Sussman和Steele在1975年定义Scheme时采用,并得到广泛应用。


你提到堆栈的问题,让我想起了 https://dev59.com/bF4b5IYBdhLWcg3wYQmZ#29226388。 - Noumenon

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