1

考虑以下代码:

function nepaliBuddha() {
    var a = 20;

    return function buddhaNepal() {
        console.log(a); 
    }
}

var closure = nepaliBuddha();

closure(); // logs 20
  1. 现在当我们调用closure输出时20。这证明内部作用域属性 ( [[scope]]) 已分配给定义或声明时声明的内部函数。如果未在声明时分配,则无法记录 20,因为它在不同的上下文中被调用

  2. 调用closure()函数上下文的作用域链是在函数调用时创建的,由当前上下文的激活对象或 VO[[scope]]和该函数的内部属性组成。

  3. 调用也会创建[[scope]]property ,这意味着在声明和执行时都会创建内部范围属性,不是吗?

  4. 通常定义说[[scope]]属性是在运行时或函数调用时创建的,但这不是真的,因为[[scope]]属性也已经在声明时分配了。

  5. 我认为[[scope]]执行函数后属性可能会更新,是吗?请给出 [[scope]] 内部属性的明确定义。在声明时或执行时或同时创建它的方式和时间。

4

2 回答 2

9

哇,你是不是完全糊涂了。好吧,我会尽量简单地解释闭包。

首先,我们将从范围开始。有两种类型的范围:

  1. 块范围
  2. 功能范围

块作用域在程序中出现时立即开始。另一方面,函数作用域直到函数被调用才开始。因此,对同一函数的多次调用会导致创建多个范围。

JavaScript 没有块作用域。它只有功能范围。因此,要模拟块作用域,我们需要创建一个函数表达式并立即执行它。此模式称为立即调用函数表达式 (IIFE),如下所示:

(function () {
    // this is the JS equivalent of a block scope
}());

除了块作用域和函数作用域之外,还有另一种对作用域进行分类的方法。因此我们还有:

  1. 词法作用域
  2. 动态范围

这种区别仅适用于函数作用域,因为块作用域始终是词法作用域。JavaScript 只有词法范围。

要了解词法作用域和动态作用域之间的区别,我们需要了解自由变量和绑定变量之间的区别。

  1. 自由变量是在函数中使用但未在该函数中声明的变量。
  2. 在函数中声明的变量被称为绑定到该函数。

考虑以下程序:

function add(x, y) {
    return x + y; // x and y are bound to add
}

在上面的程序中,变量xy绑定到函数 add 因为它们是在add.

另一方面,变量xy以下程序中的变量在函数内是自由的,add因为它们没有在函数内声明,add但它们在函数内使用add

function add() {
    return x + y; // x and y are free within add
}

现在自由变量是个问题。它们需要映射到某个值,但是哪个值?这就是词法作用域和动态作用域发挥作用的地方。我不会详细介绍主要细节,但您可以在 Wikipedia 上阅读相关内容。

范围很像原型继承。当一个新的作用域开始时,它从父作用域继承,形成一个作用域链,很像 JavaScript 中的原型链。

词法作用域和动态作用域在新作用域继承形式的父作用域方面有所不同。

  1. 在词法作用域中,新函数作用域继承自定义该函数的作用域(即其词法环境)。
  2. 在动态作用域中,新函数作用域继承自调用该函数的作用域(即调用作用域)。

由于 JavaScript 只有词法作用域,我们不会为动态作用域而烦恼。考虑以下程序:

var count = 0;

function incrementCount() {
    return ++count;
}

(function () {
    var count = 100;
    alert(incrementCount()); // 1
}());

这里函数incrementCounter有一个自由变量 - count。由于 JavaScript 具有词法作用域count,因此将映射到全局变量而不是IIFE 中声明count的局部变量。count因此incrementCount返回1而不是101

现在闭包仅适用于具有词法作用域的语言。考虑以下程序:

function getCounter() {
    var count = 0;

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

var counter = getCounter();

alert(counter()); // 1
alert(counter()); // 2
alert(counter()); // 3

在上面的程序中,返回的函数getCounter是一个相对于变量的闭包,count因为:

  1. 该变量count在返回的函数中是自由的(即counter)。
  2. 该函数被移到声明的范围之外count

这两个条件都是函数被称为闭包的必要条件。有关更多信息,请阅读以下答案:https ://stackoverflow.com/a/12931785/783743

现在要理解的重要一点是,counter即使它可能永远不会被调用,该函数仍将被称为闭包。闭包只是一个关闭变量的函数(称为闭包的上值)。

当我们调用时,getCounter我们创建了一个新的作用域(我们称之为这个作用域A),每次我们调用由getCounter(ie counter) 返回的函数时,我们都会创建一个继承自作用域的新作用域A。就这样。不会创建新的闭包。

于 2013-07-08T06:39:55.503 回答
1

Aclosure是一种特殊的对象,它结合了两件事:一个函数,以及创建该函数的环境。环境由创建闭包时在范围内的任何局部变量组成。

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

现在打电话makeFunc()

var myFunc = makeFunc();
myFunc();

在这种情况下,myFunc是一个包含函数和创建displayName闭包时存在的“Mozilla”字符串的闭包,MDN

所以,scope当我调用var myFunc = makeFunc();myFunc()makeFunc()调用的结果)时创建的现在是一个闭包。所以,转到第一行

Aclosure是一种特殊的对象,它结合了两件事:一个函数,以及创建该函数的环境。

现在考虑这些

function nepaliBuddha() {
    var a = 1;
    return function buddhaNepal() {
        a = a+1;
        console.log(a);  
    }
}
var closure1 = nepaliBuddha(); // An individual scope
var closure2 = nepaliBuddha(); // An individual scope

closure1(); // 1 
closure1(); // 2

closure2(); // 1
closure2(); // 2
closure2(); // 3

演示。

这意味着,closure1()并且closure2()是闭包,它们都有自己的范围/环境,并且一旦获得它们就可以访问自己的范围(在这种情况下,每次调用时,nepaliBuddha您都在创建闭包并将其提供/保存到变量中)。

范围定义了函数、变量等可用的区域。因此,当您在(外部函数buddhaNepal)内部定义/声明函数(内部函数)时, (内部函数)刚刚与全局范围分离,仅此而已。它无法访问全局范围内的任何内容,但它有自己的范围,仅此而已。(外部函数)是(内部函数)的边界,在这种情况下,外部函数的局部范围/环境是(内部函数)的全局范围。nepaliBuddhabuddhaNepalnepaliBuddhabuddhaNepalnepaliBuddhabuddhaNepal

在 JavaScript 中,这被称为Lexical Scopeing定义变量名称在嵌套函数中的解析方式。词法作用域的其他名称是静态作用域或闭包。这意味着内部函数的范围包含父函数的范围。

于 2013-07-08T07:19:11.247 回答