JavaScript 闭包的全面定义

4

我已经阅读了几十篇有关闭包的stackoverflow参考资料、MDN文档和其他博客文章。他们似乎都根据自己的理解给出了闭包的定义。例如,根据MDN文档:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

以下是对闭包的解释:

通常,函数内部的本地变量仅在该函数执行期间存在。一旦makeFunc()执行完毕,我们可以合理地期望name变量将不再可访问。然而,显然这不是这种情况。

这个谜题的解决办法就是myFunc已经成为一个闭包。闭包是一种特殊的对象,它结合了两个东西:一个函数和创建该函数的环境。该环境由在创建闭包时处于范围内的任何本地变量组成。在这种情况下,myFunc是一个闭包,它包含displayName函数和创建闭包时存在的"Mozilla"字符串。

以下StackOverflow帖子回答了闭包作为可见范围栈的问题。 What types of scope exist in Javascript?

我所困惑的是:闭包是一个对象吗?还是只是“异常作用域情况”,其中内部嵌套函数可以访问定义在其外部但是在父容器函数中局部的变量,甚至在父函数执行后仍然有效?闭包是指向像myFunc这样的嵌套函数(作用域)的对象,还是指嵌套函数本身?


我倾向于将此问题视为重复。但是,您似乎已经进行了一些研究。也许这个问题和答案可以帮助澄清您的疑虑:https://dev59.com/UWcs5IYBdhLWcg3wWClB - Aadit M Shah
@AaditMShah非常感谢!我已经阅读了您关于闭包和作用域的答案,它们非常棒!感谢您抽出时间回答像我这样困惑的人的问题!! - Athapali
你也阅读了JavaScript 闭包是如何工作的? - Bergi
一个定义?“闭包是一个带有对其自由变量所在作用域的引用的函数对象”。严格来说,我们仅在自由变量不是全局的函数中使用该术语。 - Bergi
3个回答

2
简而言之,嵌套函数中的函数可以访问外部函数声明的变量。如果该函数在全局上下文中,则显然可以访问全局变量。
更多内容:
var v1; // I'm accessible anywhere    
function a() {
    var v2;
    function b() { // I can access v2 and v1
        var v3;
        function c() {  // I can access v1, v2, v3
            var v4;
        }
        return c;
    }
    return b();
}
var f = a();

在上面的代码中,abc 都是闭包,它们可以访问其各自的父级作用域,这种递归一直持续到 window 或全局上下文。
通常来说,每个函数都是一个闭包。但只有在实现某些依赖于闭包的功能(比如工厂函数)时,我们才会想到使用它们。

1
在这篇文章中给出的例子中定义了多少个闭包?http://stackoverflow.com/questions/12543395/what-types-of-scope-exist-in-javascript?rq=1。是一个闭包还是五个闭包? - Athapali
3
从技术上讲,JavaScript 中的每个函数都是闭包,因为 JavaScript 中的每个函数都携带着它的词法环境。然而,只有当闭包中所引用的值超出其作用域时(这是使闭包显著的原因),闭包才变得有趣。 - Aadit M Shah
++ 但是停止编辑!你的回答变得不够简洁了!:-D 顺便说一句,最后一个观点非常出色。 - G. Cito

1
你可以将JS函数想象成这样的结构:
class Function : Object {
  bytes      bytecode;
  varframe   varsAndArgs;
}

class varframe{
  array<value>  values;
  ptr<varframe> parent;
}

每个在JS中的函数实例实际上都是一个闭包。在顶层函数中,父指针为null。 因此,当您定义…
function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

displayName(常量/变量)将包含类Function的实例,该实例将包含对其自身varframe结构的引用:

varframe(displayName) 
  values[0] // empty array, no variables in it
  parent -> varframe(makeFunc) 
              values[1] // one variable "name" at index 0;
              parent = null    

因此,闭包是一种结构,它保存对代码和变量框架链(又称调用框架)的引用。

0
过去,许多课程材料和参考资料强调JavaScript的面向对象方面,有时忽视了函数式方法。我认为随着框架和大量JS库的发展,这种情况开始改变。《JavaScript忍者秘籍》认为,掌握函数是有效使用JavaScript的基本部分。在《忍者秘籍》中,闭包的定义更加普遍:Secrets of the JavaScript Ninja

"... closures allow a function to access all the variables, as well as other functions, that are in scope when the function itself is declared."

                                         -- "Chapter 5: Closing in on closures"

在讨论闭包时,Effective JavaScript(更或者说)这样表述:

  • 函数可以引用其作用域外定义的变量;
  • 函数可以引用另一个函数返回后定义的变量(因为在JavaScript中函数是对象);
  • 函数可以包含内部函数,这些函数被“封闭”,并且可以修改封闭函数的属性。

《Effective JavaScript》第2章第11条“熟悉闭包”源代码可在GitHub上找到:

闭包函数的好处在于,如果您不打算显式调用它,则无需名称:它可以是匿名的,并且可以作为外部函数的一部分进行评估。

1
可以引用另一个函数返回的变量”听起来不太对。一个函数不能返回一个变量。 - Bergi
改变了它...一个函数是一个可以拥有一组定义属性的对象,而闭包可以存储对它们的引用... - G. Cito

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