首页 > 新闻 > JavaScript Closure:它是什么以及你如何使用它?

JavaScript Closure:它是什么以及你如何使用它?

2023-01-11 小编

JavaScript 中的闭包与你最初想象的有着非常不同的含义,而且它也更有趣。

一位年轻女性正在研究闭包的工作原理,以及如何使用闭包来增强她为软件创建的代码。

在这篇文章中,您将发现JavaScript闭包是什么以及如何在编程代码中使用它。您还将学习在 JavaScript 中执行闭包的多种方法。最后,您将看到有关如何完成闭包以支持所学内容的代码示例。

让我们开始吧。

立即下载:Java & JavaScript 简介

JavaScript 中的闭包是什么?

JavaScript 中的闭包是一种词法范围形式,用于在函数的内部作用域中保留函数外部作用域中的变量。词法范围是用于按变量在源代码中的位置定义变量范围的过程。观看以下视频以了解有关 JavaScript 闭包的更多信息。

定义函数时,函数中的任何变量都只能在函数内使用。尝试从外部访问函数中的变量将导致范围错误;这就是关闭派上用场的地方。

让我们看一下作用域,其中包含一个代码示例,该示例展示了在全局和局部作用域中声明的变量。

let message = 'Hello';function buildGreeting() { 
    let audience = 'World';
    console.log(message + ' '+ audience);}	

在上面的示例中,有两个作用域;第一个是全局作用域,其中声明了变量“name”。第二个是函数的局部作用域,其中声明了变量“message”。在此代码中,函数可以访问变量“name”;但是,函数的局部变量只能由函数访问。如果尝试访问函数的局部变量,代码将在响应中引发错误。

let message = 'Hello';function buildGreeting() { 
    let audience = 'World';let message = 'Hello';function buildGreeting() { 
    let audience = 'World';
    console.log(message + ' '+ audience);}console.log(audience);	

此行为很重要,因为它可以帮助您了解如何避免代码中的错误,并有助于解释词法范围的工作原理。有趣的是,词法作用域允许嵌套作用域可以访问其外部函数中的变量。让我们看看下面的代码示例中是什么样子的。

从技术上讲,在此级别声明的任何函数都是嵌套作用域。在全局级别声明的每个函数都有自己的作用域,无法从全局作用域访问。此外,无法从其他函数访问函数的作用域。让我们在下面的代码块中看一下。

function buildGreeting() {
   let message = 'Hello';}function greetUser() {
   let audience = 'World';
   console.log(message);}greetUser();	

尝试运行上面的代码会导致在调用 greetUser() 函数时出现错误消息。

Uncaught ReferenceError: message is not defined

at https://cdpn.io/cpe/boomboom/pen.js?key=pen.js-321fa803-7e68-0f06-20cf-e8f66a542b59:7	

发生错误后,代码将停止运行,以帮助保持调试简单,并更轻松地识别错误消息的原因。

更多关于 JavaScript 范围的信息

在以下示例中,有两个嵌套作用域的实例;第一个是全局作用域,之所以如此命名,是因为它是最高级别的作用域。

let message = 'Hello';function buildGreeting() {
   console.log(message);
   let audience = 'World';

   function greetUser() {
        console.log(message + ' ' + audience);
    }

    greetUser();}buildGreeting();	

全局作用域是声明消息变量的位置。greetUser() 函数存在于 buildGreeting() 函数中;这意味着它是其父函数的本地函数。

JavaScript 闭包

由于这种本地作用域,这也意味着 greetUser() 函数在全局范围内不可用。但是,有趣的是,我们可以通过buildGreeting()函数访问greetUser()函数。

要使 greetUser() 函数可从全局范围访问,请从 buildGreeting() 函数中返回 greetUser() 函数。然后,将 buildGreeting() 函数分配给一个变量,并像函数一样调用该变量。

function buildGreeting() {
    let message = 'Hello';

    function greetUser() {
        console.log(message);
    }

    return greetUser;}let hello = buildGreeting();hello();	

在 JavaScript 中创建闭包的方法还有很多,在下面的代码块中,您可以看到一个您可能会发现自己使用的示例。

function buildGreeting(message) {
   return function(audience){
        return message + ' ' + audience;
   }}let greeting1 = buildGreeting('Hi');let greeting2 = buildGreeting('Hello');console.log(greeting1('User')); // Hi Userconsole.log(greeting2('World')); // Hello World	

在此示例中,您将创建一个名为 buildGreeting() 的函数,并返回一个返回由两个变量组成的字符串的函数。变量在此代码中的两个点传入。第一个是在将函数分配给变量时传入的,如下所示。

let greeting1 = buildGreeting('Hi');	

赋值后,将变量称为函数,该函数传入以下值作为内部函数的参数。在这种情况下,函数调用在控制台日志中执行,允许您查看内部函数创建的字符串。

console.log(greeting1('User')); // Hi User	

JavaScript 闭包和循环

在使用循环时,在 JavaScript 中创建闭包会变得稍微复杂一些,因为它会导致不良行为。例如,考虑以下函数,该函数在循环中使用 setTimeout 函数。

for (var id = 0; id < 3; id++) {
    setTimeout(function () {
        console.log('seconds: ' + id);
    },id * 1000);}	

在上面的代码中,循环运行三次,setTimeout 函数等待指定的时间过去,然后再运行其中的代码。您可能希望代码运行三次,以反映该循环时间 id 的索引值。

"seconds: 0""seconds: 1""seconds: 2"	

但是,在这种情况下,循环将运行,id 变量也会相应地更新。由于代码从 setTimeout 函数运行,因此 id 已更新为其最大值。由于循环的所有三个迭代共享相同的作用域,因此 setTimeout 函数会创建一个由每个循环共享的闭包。

这一事实意味着打印的消息不是您所期望的。相反,控制台日志反映 id 的最终值。

"seconds: 3""seconds: 3""seconds: 3"	

ES6 让关键字

你可以缓解这个问题,你可以使用 JavaScript ES6 let 关键字来确保 if 块中的代码按预期运行。使用 let 关键字为每个循环迭代创建一个新范围来声明索引值。让我们在下面的示例中看看如何做到这一点。

for (let id = 0; id < 3; id++) {
    setTimeout(function () {
        console.log('seconds: ' + id);
    },id * 1000);}	

上面的代码会导致预期行为,而不是在循环完成后运行的 setTimeout 函数。循环运行,setTimeout 函数为循环的每次迭代分配 id。您可以在下面看到结果输出。

"seconds: 0""seconds: 1""seconds: 2"	

IIFE和关闭

避免循环中闭包此问题的另一种方法是使用 IIFE(立即调用的函数表达式)语法,该语法会在循环运行时立即调用 setTimeout 函数。因此,与其实质上堆叠 setTimeout 函数并等待循环完成,然后执行代码,setTimeout 在循环启动后立即运行,这是预期的行为。让我们看看 IIFE 的语法如下所示。

for (var id = 1; id <= 3; id ++) {
    (function (id) {
        setTimeout(function () {
            console.log('seconds: ' + id);
        }, id* 1000);
    })(id);}	

在上面的代码中,循环运行,并在每次迭代时立即调用该函数。然后,setTimeout 函数立即开始执行,在每次迭代中保留 id 的状态。但是,值得注意的是,ES6 方法是解决此问题的更清晰的解决方案;但是,有时IIFE可能会更好地工作。

如何推进 JavaScript 闭包

在学习任何编程概念时,前进的最好方法是练习你学到的东西。闭包的实践更为关键,因为主题可能很棘手。由于这一事实,探索甚至创建闭包以了解闭包在不同情况下的外观是有益的。每种情况都略有不同,您可以使用闭包来执行许多任务,否则这些任务会困难得多。识别闭包是巩固您对闭包以及如何创建闭包的理解的最佳方式。


*必填

您好,访客!有什么新鲜事想告诉大家?

点击按钮快速添加回复内容: 高兴 支持 激动 给力 加油 生气 路过 威武
发表
暂时还没评论,等你发挥!