【JavaScript】JavaScript 中的闭包:从入门到精通

简介: 【JavaScript】JavaScript 中的闭包:从入门到精通

🌟 JavaScript 中的闭包:从入门到精通

👋 闭包作为JS中非常重要的特性,对于理解程序执行上下文、内存管理乃至实现模块化编程都有着至关重要的作用。本文将以由浅入深的方式带你一步步揭开闭包的神秘面纱!

📚 基础知识

💡 什么是闭包?

首先,我们以一个简洁的定义开始:闭包(Closure)JavaScript中的一种机制,它允许一个内部函数访问并操作其外部作用域(包含全局作用域和外层函数作用域)的变量,即使在其外部函数已经执行完毕之后,这些变量依然能够保持活跃状态并通过内部函数访问。

📚 闭包的组成

闭包主要由三个关键部分构成:

1️⃣ 内部函数 - 在另一个函数内部定义的函数。

2️⃣ 外部函数 - 包含内部函数的函数。

3️⃣ 外部函数的作用域 - 外部函数内的变量环境。

🧪 示例一:闭包的基本形式

function outerFunction() {
  const outerVar = 'I am from outer world'; // 外部作用域变量
  function innerFunction() {
    console.log(outerVar); // 内部函数访问外部作用域变量
  }
  // 调用内部函数,使其形成闭包
  innerFunction();
}
// 执行外部函数
outerFunction(); // 输出: "I am from outer world"

在上述例子中,innerFunction 访问了其外部作用域的 outerVar 变量,虽然 outerFunction 执行完毕,但因为调用了 innerFunction 导致 outerVar 仍然保留在内存中,这就是一个基本的闭包应用场景。

🌊 示例二:闭包的持久化访问

闭包更为有趣的一面在于,它可以持久保存对外部作用域变量的访问权,即便外部函数已经执行结束:

function createGreeting(prefix) {
  let greetingPrefix = prefix; // 外部作用域变量
  
  return function(name) {
    console.log(greetingPrefix + ', ' + name);
  };
}
// 创建闭包
const sayHello = createGreeting('Hello');
// 多次调用闭包
sayHello('Alice'); // 输出: "Hello, Alice"
sayHello('Bob');   // 输出: "Hello, Bob"
// 注意:尽管 createGreeting 函数早已执行完毕,但 sayHello 仍能访问 greetingPrefix

这里,createGreeting 函数返回了一个内部函数,该内部函数持有了 greetingPrefix 的引用。当我们在外部多次调用 sayHello 函数时,它依然能够访问最初传入的 'Hello'

🔨 闭包的实际应用

闭包在实际开发中有许多重要用途,如:

  • 数据私有化:通过闭包,我们可以创建拥有私有变量的类或模块。
  • 事件监听:在JavaScript中,事件处理器就是一个典型的闭包应用,它可以访问绑定事件时的作用域中的变量。
  • 异步编程:例如,在回调函数、Promise、async/await等场景中,闭包有助于保持对异步任务相关变量的访问。

🧠 深入理解

🏗️ 闭包与作用域链

作用域链(Scope Chain) 是JavaScript引擎用于决定变量查找顺序的一系列作用域。每个函数都有自己的执行上下文,其中包含了变量对象(Variable Object,现代浏览器中为LexicalEnvironment),而闭包正是基于作用域链这一机制得以实现。

当一个函数被执行时,它会创建一个新的作用域,同时将当前作用域及其所有父级作用域(直至全局作用域)串联起来,形成一个作用域链。因此,内部函数可以通过作用域链访问到其外部函数的作用域变量,这种跨越作用域层级的访问能力正是闭包的关键所在。

🎯 持久化变量与垃圾回收

闭包的一个显著特征是它可以记忆外部作用域的状态。这意味着即使外部函数执行完毕,只要还有闭包引用这些外部变量,这些变量就不会被垃圾回收机制清理掉。例如:

function counterFactory() {
  let count = 0;
  // 内部函数,它构成了闭包
  return function() {
    count += 1;
    return count;
  };
}
const myCounter = counterFactory();
console.log(myCounter()); // 输出:1
console.log(myCounter()); // 输出:2

在此例中,counterFactory 函数每次调用都会生成一个新的闭包实例,闭包中的 count 变量会在每次调用内部函数时累加。由于闭包保留了对 count 的引用,所以 count 不会被当作垃圾回收。

🔐 数据隐藏与封装

闭包还提供了天然的数据封装手段,这对于实现面向对象编程中的私有属性特别有用。通过闭包,可以在不使用严格模式或者ES6的Symbol之类的语法特性下,模拟出私有变量的效果:

function BankAccount(initialBalance) {
  let balance = initialBalance;
  function deposit(amount) {
    balance += amount;
  }
  function withdraw(amount) {
    if (balance >= amount) {
      balance -= amount;
      return true;
    }
    return false;
  }
  // 公共接口,暴露deposit和withdraw方法,而不直接暴露balance
  return {
    deposit,
    withdraw
  };
}
const account = BankAccount(1000);
account.deposit(500); // 存款成功,balance现在是1500
console.log(account.withdraw(200)); // 提款成功,balance现在是1300

在这个BankAccount构造器中,balance 变量是私有的,只能通过暴露的 depositwithdraw 方法间接访问和修改。

📡 异步处理与回调函数

在JavaScript异步编程模型中,闭包广泛应用于事件处理、定时器、Promise回调、Async/Await等场合,用来维护异步执行所需的上下文信息:

function delayMessage(message, delay) {
  setTimeout(function timer() {
    console.log(message);
  }, delay);
}
delayMessage('Hello after 2 seconds', 2000);

在这段代码中,setTimeout回调函数 timer 是一个闭包,它在延迟结束后仍然记得 message 参数的值。

🛠️ 高级用例与注意事项

闭包的强大之处不仅体现在上述基本场景,它还可以用于各种复杂情况,如缓存策略模块设计函数工厂等。然而,需要注意的是,过度使用闭包可能会导致内存占用增加,特别是当闭包持续引用大量数据且不再需要时,应适时解除引用以避免内存泄漏。此外,对闭包中变量状态的管理也需要格外小心,以防止因并发访问而导致的问题。

目录
相关文章
|
3月前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
801 2
|
3月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
26天前
|
JavaScript 前端开发
【JavaScript】——JS基础入门常见操作(大量举例)
JS引入方式,JS基础语法,JS增删查改,JS函数,JS对象
|
2月前
|
JavaScript 前端开发
js 闭包的优点和缺点
【10月更文挑战第27天】JavaScript闭包是一把双刃剑,在合理使用的情况下,它可以带来很多好处,如实现数据封装、记忆功能和模块化等;但如果不注意其缺点,如内存泄漏、变量共享和性能开销等问题,可能会导致代码出现难以调试的错误和性能问题。因此,在使用闭包时,需要谨慎权衡其优缺点,根据具体的应用场景合理地运用闭包。
128 58
|
2月前
|
缓存 JavaScript 前端开发
js 闭包
【10月更文挑战第27天】JavaScript闭包是一种强大的特性,它可以用于实现数据隐藏、记忆和缓存等功能,但在使用时也需要注意内存泄漏和变量共享等问题,以确保代码的质量和性能。
47 7
|
2月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
339 1
|
2月前
|
监控 前端开发 JavaScript
React 静态网站生成工具 Next.js 入门指南
【10月更文挑战第20天】Next.js 是一个基于 React 的服务器端渲染框架,由 Vercel 开发。本文从基础概念出发,逐步探讨 Next.js 的常见问题、易错点及解决方法,并通过具体代码示例进行说明,帮助开发者快速构建高性能的 Web 应用。
129 10
|
2月前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
28 2
|
2月前
|
数据采集 存储 JavaScript
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
本文介绍了如何使用Puppeteer和Node.js爬取大学招生数据,并通过代理IP提升爬取的稳定性和效率。Puppeteer作为一个强大的Node.js库,能够模拟真实浏览器访问,支持JavaScript渲染,适合复杂的爬取任务。文章详细讲解了安装Puppeteer、配置代理IP、实现爬虫代码的步骤,并提供了代码示例。此外,还给出了注意事项和优化建议,帮助读者高效地抓取和分析招生数据。
105 0
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
|
2月前
|
存储 缓存 自然语言处理
掌握JavaScript闭包,提升代码质量与性能
掌握JavaScript闭包,提升代码质量与性能