📜  描述 JavaScript 中的闭包概念

📅  最后修改于: 2022-05-13 01:56:25.976000             🧑  作者: Mango

描述 JavaScript 中的闭包概念

在本文中,您将了解 Javascript 中的闭包概念以及如何将其应用于各种函数。 javascript 中的闭包是一种组合所有函数并将它们与词法环境捆绑在一起的方法。随着本文的深入,我们将深入探讨闭包概念。

先决条件:在我们开始我们的主要讨论之前,你应该有一个 良好的 javascript 函数工作知识,即内存分配方式、变量范围概念、javascript 中的词法环境。

让我们首先开始讨论基本的 javascript 程序示例:

如果您不知道如何在 HTML 文档中添加和执行 javascript,请参阅在 HTML 文档中放置 JavaScript 的位置?文章以获取更多详细知识。

我们将快速查看 javascript 代码是如何执行的。为此,在index.html文件中添加   



script.js
function a() {
  var x = 5;
  console.log(x);
}
  
function b() {
  var y = 15;
  console.log(y);
}
a();
b();


script.js
function a() {
  var x = 5,
    y = 6;
  console.log(x);
  
  function b() {
    var z = 7;
    console.log(y);
    console.log(z);
  }
  b();
}
a();


script.js
function a() {
  console.log("Hello, I am a function");
  
  function b() {
    console.log("Hello, I am an inner function");
  }
  return b;
}
const result = a();
result();


script.js
function outer() {
  var x = 5;
  
  function inner() {
    console.log("Hello, I am an inner function");
    console.log(
      "I am accessing var x of my parent function"
      + " when that parent functions execution "
      + "context destroyed"
    );
    console.log(x);
  }
  return inner;
}
const result = outer();
result();


script.js
function outer(secret) {
  const notAccessibleByAnyone = secret;
  
  return function inner() {
    console.log(notAccessibleByAnyone);
  };
}
  
const closureFunction = outer("Secret Data");
closureFunction();
console.log(notAccessibleByAnyone);


script.js
function partialApplication(multiplier) {
  return function inner(array) {
    let size = array.length;
    for (let i = 0; i < size; i++) {
      array[i] = array[i] * multiplier;
    }
    return array;
  };
}
  
const multiplyBy2 = partialApplication(2);
const arr1 = multiplyBy2([1, 2, 3]);
console.log(arr1);
  
const multiplyBy4 = partialApplication(4);
const arr2 = multiplyBy4([1, 2, 3]);
console.log(arr2);


现在,我们将通过示例了解所有基本概念,例如函数如何工作,如何使用作用域链在另一个函数中声明函数,从函数返回函数,然后将了解闭包概念。

示例 1:在这里,我们只是简单地说明函数的工作原理,如下所示:

脚本.js

function a() {
  var x = 5;
  console.log(x);
}
  
function b() {
  var y = 15;
  console.log(y);
}
a();
b();

解释:

  1. 首先,将创建一个全局执行上下文,然后函数定义将在内存中获得空间。
  2. 一旦遇到 javascript,第一个函数调用 a(),它会创建另一个执行上下文并为变量 x 保留内存并将 undefined 放入 x 中。
  3. 然后执行阶段的线程进入画面,变量 x 得到值 5,在下一行 console.log 中打印 x 的值,最后当函数执行完成时,它从调用堆栈和执行上下文中删除存储的变量 x 被销毁,函数b() 也是如此。
  4. 但是在这里我们不能访问块外的变量 x 和 y,因为当函数完成它的执行上下文时,一个执行上下文也会被删除,当 x 消失时,它在内存中就没有了。函数b() 也是如此,我们无法访问该函数之外的 var y。

输出:

示例 2:在本节中,我们将讨论函数内部使用作用域链访问变量的函数。

脚本.js

function a() {
  var x = 5,
    y = 6;
  console.log(x);
  
  function b() {
    var z = 7;
    console.log(y);
    console.log(z);
  }
  b();
}
a();

解释:

  1. 这里创建了一个执行上下文,然后函数a() 在内存中获取空间。
  2. 之后我们调用函数a(),所以它有自己的执行上下文。
  3. 还有一个变量x函数b() ,它在内存中获得一个空间,稍后在代码执行阶段,变量 x 被打印出来
  4. 一旦控制调用函数b(),它就会在最后一个执行上下文中获得另一个执行上下文,并且 var y 在内存中获得空间,然后它遇到一个 console.log(x) 但 x 不在执行上下文中函数b() 所以它会因为作用域链而进入父词法环境,并希望在那里找到它并打印 x 的值,
  5. var y 是函数内部的东西,因此无需任何额外的努力就可以打印出来。

输出:

示例 3:在这种情况下,我们将讨论函数。

脚本.js

function a() {
  console.log("Hello, I am a function");
  
  function b() {
    console.log("Hello, I am an inner function");
  }
  return b;
}
const result = a();
result();

解释:

  1. 创建全局执行上下文,结果和函数a() 获得分配的内存空间。
  2. 稍后在调用函数a() 时,会创建另一个执行上下文,并在该内存空间中分配函数b()。
  3. 最后,在打印一行并返回一个函数之后,a() 完成其执行并将其从调用堆栈中删除。此外,它的执行上下文也会被删除。
  4. 函数a() 的结果存储在 const 变量中。
  5. 我们已经成功调用了函数result(),结果,函数b() 中返回的功能被执行了。

输出:

示例 4:这里是闭包部分。假设您不仅有一个简单的 console.log,而且还在函数b() 中声明了一些变量。现在,在这里停下来想想代码的输出。

脚本.js

function outer() {
  var x = 5;
  
  function inner() {
    console.log("Hello, I am an inner function");
    console.log(
      "I am accessing var x of my parent function"
      + " when that parent functions execution "
      + "context destroyed"
    );
    console.log(x);
  }
  return inner;
}
const result = outer();
result();

解释:

  1. 创建全局执行上下文,变量结果和函数a() 获取内存中的空间。
  2. 在线程执行阶段,首先调用a()函数。
  3. 创建了一个新的执行上下文 var x 并且函数b 获得了内存空间,并且在它返回函数b 之后,这意味着我们将结果作为函数的代码,现在函数a() 从调用堆栈中删除它的执行上下文也被破坏了。
  4. 在最后一行中,当我们调用函数result() 时,它成功开始执行其中的函数代码。但是一旦它遇到一个不在该函数的执行上下文中的变量,所以它会尝试在外部范围内找出 var x 但是等待,我们无法访问函数a () 的执行上下文, 那么这个 x 将如何被执行呢?
  5. 这是否意味着 var x 将变得未定义?不,因为每当我们从另一个函数公开或返回函数时,这就是闭包的概念,不仅是返回的代码,而且它伴随着一个特殊的东西,称为词法环境,即其外部父函数的周围环境。
  6. 因此,函数b() 从函数返回时,会随其父级的词法环境一起出现,因此始终可以访问变量 x 的引用。注意最后一行“总是可以访问变量的引用”而不是值。

输出:

定义:闭包是函数与其词法环境捆绑在一起的组合。词法环境是本地函数内存和对父词法环境的引用。换句话说,即使在父函数已经执行之后,函数仍保持父作用域的环境时,就会创建闭包。

这种对父词法环境的引用是闭包函数可以访问外部函数变量的原因,即使这些函数不在调用堆栈中,或者我们可以说即使外部函数已关闭。

如何创建闭包:每当在创建另一个函数时创建某个函数时,就会创建闭包。在上面的例子中,函数inner 是在创建函数a() 时创建的。

几个用例和闭包示例:

1. 隐私:每当我们必须隐藏某些功能或变量作为封装时,我们将其包装在父级的词法环境中。

示例:以下示例是使用闭包进行封装的基本模板。

解释:

  1. 所有全局执行上下文都将在内存分配阶段创建,函数outer() 和 const 闭包函数将获得内存空间。
  2. 在线程执行时,函数外部的代码将开始执行,因为它是代码的第一行。
  3. 现在将创建另一个执行上下文,并且 const notAccessibleByAnyone将获得空间,然后外部函数返回内部函数代码,执行上下文将消失。请注意, const 变量不在执行上下文中。
  4. 但是闭包函数引用了那个变量,所以这就是用闭包封装的整个概念。

脚本.js

function outer(secret) {
  const notAccessibleByAnyone = secret;
  
  return function inner() {
    console.log(notAccessibleByAnyone);
  };
}
  
const closureFunction = outer("Secret Data");
closureFunction();
console.log(notAccessibleByAnyone);

输出:在第一行, Secret Data在被闭包访问时被打印出来,而在下一行,当来自外部的任何人试图访问它时,javascript 会抛出 ReferenceError。

2. 部分函数:当我们必须创建某种存在共同模式的功能时,我们创建一个接受较少参数的父函数,并创建一个接受比父函数更少参数的内部函数。这个概念也称为高阶函数,我们从函数。

示例:下面的示例提供了一个基本的 说明这个偏函数的样子。  

解释:

  1. 在这里,我们创建了一个父函数,它接收一个乘数作为参数,然后将它传递给一个内部函数。
  2. 稍后,当我们调用父函数时,它会返回内部函数的代码,该代码始终可以访问在父函数调用期间提供的乘数。
  3. 函数multiplyBy2()包含一个函数,该函数接受一个数组并在其上运行 for 循环,稍后返回修改后的数组,其中元素乘以 2。
  4. 函数multiplyBy4()包含一个函数,该函数接受一个数组并在其上运行 for 循环,稍后返回修改后的数组,其中元素乘以 4。

脚本.js

function partialApplication(multiplier) {
  return function inner(array) {
    let size = array.length;
    for (let i = 0; i < size; i++) {
      array[i] = array[i] * multiplier;
    }
    return array;
  };
}
  
const multiplyBy2 = partialApplication(2);
const arr1 = multiplyBy2([1, 2, 3]);
console.log(arr1);
  
const multiplyBy4 = partialApplication(4);
const arr2 = multiplyBy4([1, 2, 3]);
console.log(arr2);

输出: