0
点赞
收藏
分享

微信扫一扫

使用css美化gradio界面

山竹山竹px 2023-08-22 阅读 51

在这里插入图片描述

😶博主:小猫娃来啦
😶文章核心:深入理解 JavaScript 中的闭包

文章目录

不理解闭包?这玩意很难?

⭐⭐⭐关键点1

闭包的两个特点:
⭐⭐⭐关键点2

  1. 闭包内的函数可以访问外部函数中定义的变量。就像是一个保险柜,只有它自己知道密码,可以打开柜门取出里面的东西。
  2. 闭包函数可以保存在其他地方并被调用,但它仍然可以访问它创建时的环境。就像是一个存钱罐,你可以把它放在任何地方,以后需要时还可以取出里面的钱。

闭包有什么用呢?

  • 封装:闭包可以帮助我们创建私有变量和函数,避免命名冲突,保护数据安全。
  • 数据保持:闭包可以让函数内部的变量在函数执行结束后依然存在,方便后续使用。
  • 回调和异步操作:闭包常用于创建回调函数,处理异步操作时能够保存一些状态信息并进行访问和更新。
  • 模块化开发:闭包可以帮助我们创建模块化的代码,把相关功能和数据封装在闭包中,提供更高层次的抽象和封装。

如果你是对闭包有深入理解的。你肯定知道,在使用时要慎重,不能滥用闭包,否则可能会导致内存泄漏和性能消耗问题。

那么为什么会导致内存泄漏并且消耗性能呢?

⭐⭐⭐关键点3

如果你不理解闭包,那么现在你应该对闭包有些认识了。总而言之,闭包是个超级大盒子。你如果是理发师,那这个大盒子里就装着各种发蜡,剪刀,洗发水,刮胡刀,剃头刀,染发剂,烫头的药水,吹风机等等。你要用什么就拿什么,这样子就会看起来非常规整,不需要满房间去找我需要的工具。但也有坏处,虽然看起来这个事归规规矩。但是如果东西太多,把某些物品压在箱底了,你想用的话,得从最上面的物品一直往下翻,会很累。如何避免呢?那就是尽可能的少放东西,一个工具用完放在固定位置,不要乱扔,避免下次用的时候难找。

所谓闭包,就是这么一个东西。

好的闭包:
在这里插入图片描述糟糕的闭包:
在这里插入图片描述

假如你是程序,你说第一张图和第二张图,哪个图里找东西费事?你喜欢哪个闭包?

所以,闭包不仅是一种技术,更是一种思维方式,它可以帮助我们编写更优雅和高效的代码,也可以让我们的生活各个方面井井有条。


闭包的定义与原理

闭包是什么

闭包是一种特殊的函数对象,它包含了函数的代码和在创建该函数时所处环境中的变量。简单来说,闭包就是一个函数和与之相关的引用的组合体。

当一个函数内部定义了另一个函数,并且内部函数可以访问外部函数的变量时,我们就可以称这个内部函数为闭包。闭包可以“记住”创建它时的环境,即使在其定义的上下文已经不存在时仍然可以访问那些变量。

这就像是一个函数带着一个包裹,包裹里面装着函数所需的数据。当我们调用这个闭包时,它会携带着这个包裹,使得内部函数能够继续访问和操作包裹里的数据。

闭包有几个重要的特点:

  • 可以捕获并访问定义它的外部函数的变量。
  • 可以在函数外部被调用,以便在不同的上下文中使用。
  • 可以被当作参数传递给其他函数,或者作为函数的返回值。

总结起来,闭包就是一个函数和它周围的状态(即定义它时所处的环境)的组合。它能够记住创建时的上下文,并允许我们在以后的任何时间访问和操作这些数据。

创建一个闭包

var a = 5;

var outerFunction = function() {
  var b = 3;

  function innerFunction() {
    return a + b;
  }

  return innerFunction;
};

var result = outerFunction();
console.log(result()); // 输出:8

我们分析一下这段代码:
首先,在全局作用域中定义了变量 a,赋值为 5
然后,定义一个匿名函数,并将其赋值给 outerFunction 变量。在匿名函数内部,定义了变量 b,赋值为 3。然后在匿名函数内部定义了 innerFunction 函数,它引用了外部函数的变量 a 和内部函数的变量 b。最后,返回内部函数 innerFunction,形成了闭包。
紧接着,调用外部函数 outerFunction 并将返回的结果赋值给 result 变量。这里实际上是获取了一个闭包,该闭包包含了 innerFunction 和它引用的外部变量 ab
最后,调用闭包中的 innerFunction 函数,它会返回 a + b 的值。在这里,a 的值是外部变量的值 5,而 b 的值是外部函数中的变量 3。因此,result() 的结果为 8

通过在函数内部定义另一个函数并引用外部函数的变量,这就成功地创建了一个闭包。闭包可以访问和操作外部函数的变量,即使外部函数已经执行完毕。这样我们可以在之后的任何时间调用闭包,并且它会使用当初创建时的上下文信息。


基于上面的例子,我们分析一下闭包的原理和工作机制

闭包是一种特殊的函数对象,它包含了函数本身以及它被创建时所处的环境(外部函数的变量)。这使得闭包可以在函数执行完毕后仍然访问和操作其外部函数的变量。

闭包的工作机制可以总结为以下几个步骤:

  • 函数定义:当一个函数内部定义了另一个函数时,内部函数就可以引用外部函数的变量。

  • 变量捕获:当内部函数引用外部函数的变量时,JavaScript 引擎会在内部函数的执行环境中创建一个变量的引用。这个引用捕获了外部函数的变量,并保存在闭包中。

  • 外部函数执行结束:在外部函数执行完成后,根据 JavaScript 的垃圾回收机制,其局部变量通常会被销毁。但是,由于内部函数仍然引用了这些变量,它们不会被回收,而是被包含在闭包中。

  • 闭包形成:当外部函数返回内部函数时,实际上返回的是该内部函数以及它所引用的外部变量的闭包。闭包包含了函数本身以及它被创建时的环境信息。

  • 闭包的使用:返回的闭包可以在之后的任何时间内被调用,它能够访问和操作外部函数的变量,即使外部函数已经执行完毕。这是因为闭包中保存了被捕获的变量的引用。

闭包的原理基于 JavaScript 的词法作用域规则和垃圾回收机制。通过利用闭包,我们可以实现一些高级的编程技巧,例如模块模式、私有变量和函数的记忆化等。同时也需要注意,由于闭包可以保留对外部变量的引用,所以在使用闭包时要小心内存管理,避免造成内存泄漏。


闭包的应用场景

模块化开发: 闭包在模块化开发中起到了封装和隐藏变量的作用,使得我们可以创建独立的模块,并且只暴露需要外部访问的接口。这种方式可以提高代码可维护性和重用性。

var counter = (function() {
  var count = 0;

  return {
    increment: function() {
      count++;
    },
    decrement: function() {
      count--;
    },
    getCount: function() {
      return count;
    }
  };
})();

counter.increment();
console.log(counter.getCount()); // 输出: 1

在上面的例子中,我们使用闭包创建了一个计数器模块。count 是一个私有变量,只有通过返回的对象才能访问它。这样可以防止外部直接修改计数值,同时提供了三个方法 incrementdecrementgetCount 来操作计数器。


事件处理: 闭包经常用于事件处理函数,它可以捕获事件发生时的上下文信息,并在之后的某个时间点执行这个函数。这使得事件处理函数可以访问当前作用域以及外部作用域中的变量。

function createButton() {
  var count = 0;

  var button = document.createElement('button');
  button.innerText = 'Click me';
  button.addEventListener('click', function() {
    count++;
    console.log('Button clicked ' + count + ' times');
  });

  return button;
}

var myButton = createButton();
document.body.appendChild(myButton);

我们创建了一个按钮,并给它添加了点击事件处理函数。事件处理函数可以访问并更新外部函数 createButton 中的变量 count,每次点击按钮时计数器就会增加。


异步编程: 闭包在异步编程中也有广泛的应用。由于 JavaScript 是单线程的,异步操作常常涉及到回调函数。而闭包可以捕获回调函数所需的上下文信息,使得在回调函数被调用时能够访问正确的变量。

function fetchData(url, callback) {
  // 发起异步请求
  fetch(url)
    .then(function(response) {
      // 根据响应处理数据
      return response.json();
    })
    .then(function(data) {
      // 调用回调函数并传入数据
      callback(data);
    });
}

function displayData(data) {
  console.log(data);
}

fetchData('接口链接', displayData); // 异步获取数据并在回调函数中显示

这个例子中,fetchData 函数使用闭包实现了异步数据的获取。在最后一步调用 fetchData 时,我们传入一个回调函数 displayData,当数据请求成功后会被调用并传入获取到的数据。


闭包与作用域

闭包与作用域之间的关系

闭包与作用域之间有着密切的关系。在理解闭包的概念时,有必要先理解作用域的概念。

作用域定义了变量和函数的可访问范围。在JavaScript中,作用域通常是通过函数来创建的。每当你声明一个函数时,都会创建一个新的作用域。

闭包是指函数能够访问其词法作用域以外的变量的能力。具体来说,当一个函数内部引用了外部作用域的变量时,即使外部函数已经执行完毕,这个函数仍然可以使用该变量。

闭包实际上是一个函数和其相关的词法环境的组合。词法环境包含了在函数定义时所存在的所有局部变量、参数和其它函数。

闭包的出现是由于JavaScript采用的是词法作用域,它在函数定义的时候就决定了变量的作用域。而不同的函数可以访问不同的作用域,从而形成了闭包。
下面的例子可以更好地说明闭包与作用域之间的关系:

function outer() {
  var x = 10;

  function inner() {
    console.log(x); // 内部函数引用了外部函数的变量x
  }

  return inner;
}

var closure = outer(); // 外部函数执行,并将内部函数返回
closure(); // 输出: 10

在这个例子中,inner 函数引用了外部函数 outer 中的变量 x。即使 outer 函数执行完毕并返回了 inner 函数,inner 函数依然可以访问和使用 x 变量。这是因为 inner 函数形成了一个闭包,包含了对 outer 函数作用域的引用。
可以总结如下:

  • 作用域决定了哪些变量可以被访问和使用。
  • 闭包可以让函数继续访问外部作用域中的变量,即使外部函数已经执行完毕。
  • 闭包是由函数和其相关的词法环境组成,它允许函数访问外部作用域中的变量。

理解闭包与作用域之间的关系对于在JavaScript中正确使用闭包非常重要。它可以帮助我们更好地封装变量、实现模块化开发并处理异步操作等。


全局作用域、函数作用域和闭包的区别

全局作用域、函数作用域和闭包是 JavaScript 中不同的概念,它们在作用域范围和变量访问方面有所不同。

  1. 全局作用域:
  • 全局作用域是在整个代码中都可访问的作用域。
  • 在浏览器中,全局作用域一般是指在 <script> 标签或外部 JavaScript 文件中定义的变量和函数。
  • 全局作用域中声明的变量可以被后续的代码任意访问和修改。
  1. 函数作用域:
  • 函数作用域是指在函数内部声明的变量只能在函数内部使用,外部无法访问。
  • 函数作用域中的变量称为局部变量,它们只在函数内部有效。
  • 每当函数被调用时,都会创建一个新的函数作用域,函数执行完毕后,其内部的变量会被销毁。
  1. 闭包:
  • 闭包是指函数能够访问其定义时所在的词法作用域,即使函数在当前作用域外被调用。
  • 闭包通过将函数及其相关的引用变量一起封装,形成一个独立的包裹,使得函数能够继续访问外部作用域中的变量。
  • 闭包常用来实现函数的状态保留和数据私有化,以及模块化编程等功能。
  • 闭包可以延长变量的生命周期,使得函数在不同的上下文中共享数据。

区别:

总结: 全局作用域是最外层的作用域,在整个代码中都可访问;函数作用域是函数内部的作用域,只在函数内部有效;闭包是函数能够访问其定义时所在的词法作用域,即使函数在当前作用域外被调用。它们在作用域范围和变量访问方面有所不同,适用于不同的编程场景和需求。


闭包对变量生命周期的影响

闭包对变量生命周期的影响是延长了变量的生命周期。在普通的函数中,函数执行完毕后,函数作用域内的变量会被销毁,无法再被访问。但是当函数形成闭包时,闭包会持有函数内部的变量及其引用,使得这些变量在函数执行完毕后仍然可以被访问和操作。

具体来说,闭包通过保留其所在词法作用域的引用,使得这个作用域中的变量不会被垃圾回收机制回收,从而延长了变量的生命周期。这意味着在闭包外部的代码仍然可以访问和修改闭包中引用的变量。

闭包对变量生命周期的影响可以有以下几个方面:

  1. 变量在函数执行完毕后仍然可访问:闭包使得函数内部的变量不会随着函数的执行完毕而被销毁,从而使得外部代码仍然可以访问和操作这些变量。这为实现某些功能(如状态保留、数据私有化等)提供了可能性。
  2. 变量的值在闭包中得到保留:闭包中引用的变量的值会被保留下来,即使该变量原本位于函数作用域中。这可以让函数在不同的上下文中共享数据,实现了一种记忆效应。
  3. 变量可能无法被垃圾回收:由于闭包持有对变量的引用,导致这些变量无法被垃圾回收机制回收。如果闭包长时间存在,而其中引用的变量又占用较大的内存空间,可能会造成内存泄漏的问题。因此,在使用闭包时需要注意及时释放不再需要的闭包。

需要注意的是,虽然闭包可以延长变量的生命周期,但过度使用闭包或者使用不当可能会导致内存泄漏和性能问题。因此,在使用闭包时需要谨慎考虑其对变量生命周期的影响,确保合理管理和释放闭包中的资源。


闭包的优点和挑战

闭包带来的优点

闭包在 JavaScript 中带来了一些重要的优点,包括封装性和数据私有性。下面是对这些优点的详细探讨:

闭包可能带来的挑战

闭包在 JavaScript 中的使用可能会带来一些挑战,包括内存泄漏和性能问题。下面是对这些挑战的讨论:

为了应对这些挑战,我们应当谨慎使用闭包,并且遵循一些最佳实践,如限制闭包的作用域范围、避免循环中创建闭包、及时释放不再需要的闭包等。此外,在开发过程中使用性能工具和内存分析工具来检测和解决潜在的问题也是很重要的。

使用闭包的注意事项

使用闭包时,以下是一些注意事项,可以帮助我们避免潜在的问题:

通过遵循这些最佳实践和注意事项,我们可以更好地使用闭包,减少潜在问题的出现,并提升代码的可读性、可维护性和性能。


闭包使用案例和实际场景

闭包在 JavaScript 中的使用非常灵活,可以应用于各种有趣的场景。以下是一些有趣的闭包使用案例和实际场景:

  1. 私有变量和封装: 闭包可以创建私有变量,通过将变量保存在闭包的作用域中,实现数据的封装和隐藏。这种模式可用于创建模块、插件或库,确保外部代码无法直接访问内部变量。
  2. 计数器和唯一标识符生成器: 闭包可以用于创建计数器函数,每次调用函数时自增计数。这在需要跟踪某个操作次数或生成唯一标识符时非常有用。
  3. 缓存机制: 闭包可以用于创建缓存函数,将函数的计算结果缓存起来,避免重复计算。这对于计算密集型操作或需要频繁调用且结果稳定的函数来说,能够提高性能。
  4. 事件处理器: 闭包经常用于创建事件处理器,在事件触发时执行特定的逻辑。闭包可以捕获事件处理器中所需的变量,并保持其状态。
  5. 循环中的异步操作: 在循环中使用闭包可以解决异步操作中的问题。典型的例子是在循环中使用闭包来处理异步请求,并确保每个请求都能正确处理。
  6. 模拟私有方法: 由于 JavaScript 中没有真正的私有方法,但使用闭包可以模拟私有方法。通过在对象中创建闭包作为方法,可以将外部无法直接访问的方法包装在闭包中,实现私有性。
  7. 实现记忆化: 闭包可以用于实现记忆化,即将函数的输入和输出进行缓存,以便在相同输入的情况下直接返回缓存的结果,提高函数执行效率。

这些案例只是闭包在实际应用中的几个例子,闭包的灵活性使其适用于各种场景。通过合理运用闭包,我们可以更好地组织和管理代码,提高代码的可读性和可维护性,并实现一些有趣和强大的功能。

学习资源推荐

  • 文章和教程:
    MDN Web 文档:MDN 提供了详细的闭包介绍和示例,是一个很好的起点。
    JavaScript 高级概念:闭包:阮一峰的博客文章,对闭包进行了深入讲解,适合进一步理解闭包的原理和应用。
    廖雪峰的 JavaScript 教程:廖雪峰的 JavaScript 教程也涵盖了闭包的内容,以简洁明了的方式介绍了闭包的原理和使用方法。
    慕课网:慕课网是国内知名的在线教育平台,提供了大量与 JavaScript 闭包相关的视频教程,如《JavaScript深入浅出》、《JavaScript进阶篇》等。
    尚硅谷:尚硅谷是国内知名的IT培训机构,他们的网站上有大量 JavaScript 相关的教程和课程,包括闭包的讲解。

  • 视频教程:
    JavaScript Closures:Kyle Robinson Young 的视频教程,通过实际案例演示了闭包的功能和实际应用。
    但需要注意,这边需要一个加速器,否则无法打开国外的网站,这就需要各位科学上网了。

  • 书籍:
    《JavaScript高级程序设计》(第4版):这本由 Nicholas C. Zakas 所著的书籍是学习 JavaScript 的经典之作,其中有一章专门讲解了函数和闭包。
    《你不知道的JavaScript(上卷)》:这本由 Kyle Simpson 所著的系列图书中,第一卷涵盖了 JavaScript 的作用域和闭包,对深入理解闭包非常有帮助。

除了这些资源之外,还建议你通过实际编写代码和阅读开源项目的源代码来进一步学习闭包。实践中遇到的问题和案例可以帮助加深对闭包的理解和应用。

记住,理解闭包需要时间和实践。持续阅读和编写代码将帮助你更好地掌握闭包的概念和技巧。

在这里插入图片描述


举报

相关推荐

0 条评论