前端(十三)——JavaScript 闭包的奥秘与高级用法探索

在这里插入图片描述

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

文章目录

  • 不理解闭包?这玩意很难?
  • 闭包的定义与原理
    • 闭包是什么
    • 创建一个闭包
  • 闭包的应用场景
  • 闭包与作用域
    • 闭包与作用域之间的关系
    • 全局作用域、函数作用域和闭包的区别
    • 闭包对变量生命周期的影响
  • 闭包的优点和挑战
    • 闭包带来的优点
    • 闭包可能带来的挑战
    • 使用闭包的注意事项
  • 闭包使用案例和实际场景
  • 学习资源推荐

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

⭐⭐⭐关键点1

想象一下你在家里做饭,准备了一些食材和炉灶。闭包就像是你在炉灶旁边放了一个小盒子,里面有你需要用到的调料和工具。

这个小盒子就是一个闭包,里面装着你做饭时所需的东西。当你开火炒菜时,你可以随意使用盒子里的调料和工具,而不需要每次去厨房找。

闭包的作用就是让你方便地使用盒子里的东西,而不需要每次都去找它们。它把函数和相关的数据打包在一起,形成一个容器,你可以随时拿来使用。

这样的话,闭包就是帮助你更方便地存储和访问函数需要的数据,就像是一个移动的小工具箱,可以随时拿来用,而不需要每次都重新准备。

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

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

闭包有什么用呢?

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

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

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

⭐⭐⭐关键点3

假设你经常旅行,每次都会随身带着一个行李箱。如果你一直把不用的物品留在行李箱里,时间久了,行李箱就会变得很重,而且占用了不必要的空间。

闭包也是一样的道理。如果你滥用闭包,将不再需要的变量存储在闭包中,并且没有及时释放它们,就会导致内存泄漏和性能问题。

内存泄漏就好像行李箱里的物品积压太多,无法清理干净,最终导致浪费资源。如果我们不断创建和使用闭包,但又没有及时释放不再需要的部分,那么这些变量将一直占据着内存,造成内存的浪费。

性能问题则类似于携带过重的行李箱,给你的旅程增加了负担。如果闭包中存储了大量的变量和函数,每次使用它们时都要在内存中查找,这将耗费更多的时间和计算资源。对于大型项目或频繁调用的代码,这可能会显著影响程序的性能。

因此,在使用闭包时,我们需要慎重对待。及时释放不再需要的资源,避免滥用闭包,以免引起内存泄漏和性能问题。就像旅行时要定期整理行李箱一样,我们也要确保及时清理不再需要的闭包,让代码更加高效和可靠。

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

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

好的闭包:
糟糕的闭包:
前端(十三)——JavaScript 闭包的奥秘与高级用法探索_第1张图片

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

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


闭包的定义与原理

闭包是什么

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

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

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

闭包有几个重要的特点:

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

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

创建一个闭包

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. 全局作用域:
  • 全局作用域是在整个代码中都可访问的作用域。
  • 在浏览器中,全局作用域一般是指在

你可能感兴趣的:(前端,JS高阶篇,前端,javascript,开发语言)