前端组件化开发

背景 不知道你有没有遇到过以下场景: 页面逻辑越来越多,代码越来越庞大,写到后面难以 hold 住所有逻辑,很容易牵一发而动全身. 你负责的页面好好的突然出现问题,查到最后是别人代码影响. 同样的逻辑在多个地方重复书写,每次一改要改一批文件 随着前端项目复杂度的急剧增加, 上面列出的几种场景就是传统开发中会出现的问题. 也是前端组件化出现的原因. 项目复杂度增加, 一个页面一个文件需要处理的内容过多. 重复性劳动多, 效率低 质量差, 不可控 组件化初探 正是由于出现了这样的问题, 为了在越来越复杂的前端项目中提高开发效率和保证开发质量, 各路大神们开始通过各种方式来尝试解决问题. 曾经非常火的 jQuery 就基于自己建立了 jQuery 插件机制. 你可以将一些常用逻辑进行封装变成 jQuery 插件, 还可以将插件开源进行共享. 比如纯手写会吐血的日期时间选择器,轮播, 多级菜单等等. 你可以在这里浏览更多jQuery插件. jQuery 插件的用法通常是: $(".select").pluginName(config) 除了 jQuery 插件模式, 还有一种常见模式是对象模式. 这种模式现在仍然有很多优秀的库在被我们直接或者间接使用. 比如: swiper 对象模式的写法: <!-- Slider main container --> <div class="swiper-container"> <!-- Additional required wrapper --> <div class="swiper-wrapper"> <!-- Slides --> <div class="swiper-slide">Slide 1</div> <div class="swiper-slide">Slide 2</div> <div class="swiper-slide">Slide 3</div> ... </div> </div> <script src="path/to/swiper.min.js"></script> <script> var mySwiper = new Swiper ('.swiper-container', { direction: 'vertical', loop: true, }) </script> 对象模式通过配置创建对象, 通常创建对象的参数中有一项是元素/元素选择器, 通过js代码将逻辑与这个元素紧密绑定. ...

August 7, 2025

前端性能监控

大纲 我们会从以下三个方向来讲解埋点与监控的知识: 什么是埋点?什么是监控? JS 中实现监控的核心方案 写一个“相对”完整的监控实例 一、什么是埋点?什么是监控? 在日常沟通中,我们经常会把【埋点】和【监控】放到一起说,但是它们在本质上是有一定的区别的: 1. 埋点 埋点主要用于收集用户行为数据。在日常开发中,我们会通过 在前端代码中插入代码或脚本的方式 来实现埋点功能。 埋点的主要作用就是:捕获特定用户行为(如点击、浏览、提交表单、页面跳转等)以及关键业务数据(如下单金额、商品类别等) 在日常开发中,埋点的实现方案大致可以分为以下三大类: 手动埋点:在代码中手动加入记录代码来捕获特定事件。 自动埋点:利用 DOM 事件代理等技术来捕获页面上所有事件,从而减少手动配置。 可视化埋点:通过工具界面标记需要采集的元素和事件,可以不用手写代码。 2. 监控 而监控则主要关注 系统的性能和稳定性。在日常开发中,我们会通过 采集页面加载时间、资源请求、错误日志等数据 的方式来实现前端监控。 监控的主要作用就是:及时发现并定位页面性能瓶颈或代码异常,目的是为了保障系统不出 bug 在日常开发中,监控一般需要完成以下三大部分: 性能监控:如:首屏加载时间、页面交互耗时、资源加载耗时等。 错误监控:捕获 JavaScript 错误、网络请求失败、资源加载异常等。 用户体验监控:收集白屏、卡顿等影响用户体验的问题等。 区别总结 维度 前端埋点 前端监控 目标 捕获用户行为数据 监控系统性能、错误、稳定性 数据类型 用户点击、表单提交、页面跳转等 页面加载时间、错误日志、卡顿情况等 实现方式 手动埋点、自动埋点、可视化埋点 错误捕获、性能指标采集 核心关注点 用户行为、业务数据 系统Bug、性能优化 二、JS 中实现监控的核心方案 根据上面所说,我们知道埋点和监控的目的存在不同,但是它们的思路确是有很多一致性的,其核心都是:获取关键的数据,发送(上报)给服务端,依据数据来解决其不同的目的。 所以,无论是埋点也好,还是监控也罢,我们都需要 获取关键位置数据。 1. 跟踪用户事件(点击、滚动等) 定义通用跟踪函数(后续事件会通过该函数完成上报):trackEvent 函数接收事件类型和事件详情,并上报到服务端。 // 用于记录或发送跟踪数据到服务器的函数 function trackEvent(eventType, details) { console.log(`Event: ${eventType}`, details); // 在控制台打印事件类型和详情 // 上报到服务端。 fetch('/测试接口地址', { method: 'POST', body: JSON.stringify({ eventType, details }) }); } 捕获按钮点击事件:获取 id 为 myButton 的按钮,并在其 click 事件上添加监听器。在按钮被点击时调用 trackEvent 函数,记录点击事件的类型(button_click)、按钮 ID 和时间戳。 ...

August 7, 2025

代码分割工程化

什么是 Bundle Splitting? Bundle Splitting(代码分割)是一种将前端资源拆分成更小、独立的模块的技术。传统上,前端资源(如 JavaScript、CSS 和图片等)被打包成单个文件,通常称为“bundle”。这种方式存在一个问题,即无论用户需要哪些功能,都必须下载整个 bundle。而 Bundle Splitting 技术通过将代码拆分为更小的模块,按需加载,可以有效解决这个问题。 Bundle Splitting 的优点 减少初始加载时间 通过将代码拆分为多个小块,可以减少初始加载时间。当用户首次访问网页时,只需下载当前页面所需的资源,而无需等待整个应用程序的加载完成。这可以极大地提高页面的响应速度,并减少用户的等待时间。 提升用户体验 通过将代码分割成模块,可以使应用程序更具响应性。当用户与应用进行交互时,只需加载所需的代码块,而不是整个应用。这将减少不必要的资源消耗,并使用户感觉应用更加流畅和快速。 优化缓存策略 拆分代码还能优化缓存策略。当应用程序发生更改时,只需重新加载发生变化的模块,而不必重新下载整个应用。这减少了不必要的网络请求,提高了应用的更新效率,并减轻了服务器的负载。 并行加载资源 通过将代码拆分为多个模块,可以实现并行加载资源。浏览器能够同时下载多个文件,从而减少整体加载时间。这对于大型应用程序和复杂的前端框架特别有益,能够更好地利用浏览器的并行下载能力,提高资源的加载效率。 代码复用和维护 通过将代码分割成模块,可以更好地实现代码的复用和维护。不同的模块可以根据需求进行加载和升级,使得代码结构更加清晰、模块化。这也有助于团队合作,不同开发者可以并行工作在不同的模块上,提高开发效率和代码质量。 Bundle Splitting 的缺点 增加了网络请求 拆分代码会增加应用程序的网络请求次数。每个模块都需要进行一次独立的网络请求,这可能会导致一些额外的延迟和性能损失。在网络条件较差的情况下,这可能会对用户体验产生一定影响。 增加了开发复杂性 使用 Bundle Splitting 技术需要对应用程序的依赖关系进行深入分析和管理。开发人员需要仔细考虑代码的拆分点,以及如何在运行时动态加载模块。这增加了开发复杂性,并要求开发人员具备较高的技术水平和经验。 潜在的代码冗余 当模块之间存在共享的代码片段时,可能会出现潜在的代码冗余问题。如果不合理地进行代码拆分,可能会导致多个模块中存在相同的代码,增加了资源的下载和维护成本。因此,在进行代码拆分时,需要仔细考虑代码复用和代码冗余的问题。 需要额外的工具和配置 Bundle Splitting 需要使用额外的工具和配置来实现代码的拆分和动态加载。这可能需要对构建工具进行配置,如 Webpack、Rollup 等,以及对模块加载器进行调整。这增加了一些学习成本和开发成本,尤其是对于新手来说。 Bundle Splitting 的适用场景 Bundle Splitting 技术适用于大型的 Web 应用程序,特别是那些具有复杂功能和大量依赖的应用。以下是一些适合使用 Bundle Splitting 的场景: 大型单页应用:对于由多个模块组成的单页应用,使用 Bundle Splitting 可以提高初始加载速度,提升用户体验。 按需加载:对于需要动态加载不同功能模块的应用,Bundle Splitting 可以根据用户的需求按需加载模块,减少不必要的资源消耗。 公共库和框架:对于使用公共库或框架的应用,可以将这些库和框架拆分成单独的模块,以便在多个页面中共享和复用。 国际化支持:对于需要支持多种语言的应用,可以将不同语言的资源文件拆分成独立的模块,根据用户的语言偏好进行加载。 代码分支管理:对于大型团队开发的项目,使用 Bundle Splitting 可以更好地实现代码的分支管理和并行开发,提高开发效率。 Bundle Splitting 在知名项目中的应用 示例 1:React.lazy 和 Suspense React.js 是一个广泛使用的 JavaScript 库,用于构建用户界面。React 提供了 React.lazy 和 Suspense API 来实现 Bundle Splitting。 ...

August 7, 2025

持续集成/持续部署(CI/CD)

传统应用发布模式 开发人员:在开发环境完成代码编写,单元测试,测试通过后提交到代码仓库 运维人员:把项目部署到测试环境,供QA团队测试,测试通过后,部署生成环境 测试人员:进行测试,测试完成后通知运维部署生产环境 缺点 项目在早期就存在错误,但到最后集成的时候才发现 需要手动操作,易错率高 开发与运维需要及时沟通 有了以上缺点,那么就有了CI/CD CI/CD 持续集成(CI): 合并开发人员正在编写的所有代码 一天内进行多次合并和提交代码 从存储库或生产环境中进行构建和自动化测试,确保没有集成问题并及早发现任何问题 持续交付(CD): 可以通过将更改自动推送到发布系统来随时将软件发布到生产环境中 持续部署,并自动将更改推送到生产中 GitLab内置CI/CD 运行流水线任务 Job 在文件中可以定义一个或多个作业,每个作业具有唯一的名称,每个作业是独立执行的,每个作业至少包含一个script stages: 用于定义作业可以使用的阶段,并且是全局定义,同一个阶段的作业并行运行,不同阶段按顺序运行 only: 用分支策略来限制jobs构建 script: 项目中package中的脚本 environment: 定义此作业完成部署的环境名称 下面是每个jobs的详细变量名 Keyword Required Description script yes Runner执行的命令或脚本 image no 所使用的docker镜像,查阅使用docker镜像 services no 所使用的docker服务,查阅使用docker镜像 stage no 定义job stage(默认:test) type no stage的别名(已弃用) variables no 定义job级别的变量 only no 定义一列git分支,并为其创建job except no 定义一列git分支,不创建job tags no 定义一列tags,用来指定选择哪个Runner(同时Runner也要设置tags) allow_failure no 允许job失败。失败的job不影响commit状态 when no 定义何时开始job。可以是on_success,on_failure,always或者manual dependencies no 定义job依赖关系,这样他们就可以互相传递artifacts cache no 定义应在后续运行之间缓存的文件列表 before_script no 重写一组在作业前执行的命令 after_script no 重写一组在作业后执行的命令 environment no 定义此作业完成部署的环境名称 coverage no 定义给定作业的代码覆盖率设置 配置.gitlab-ci.yml stages: - deploy_dev - deploy_test - deploy_production deploy_dev: stage: deploy_dev environment: name: dev only: - dev script: - rsync -av . ${WORKSPACE_PATH} && cd ${WORKSPACE_PATH} - npm run build:dev - ansible-playbook ansible-deploy.yml --extra-vars "hosts=cloud_ui_test projectDir=${WORKSPACE_DIST_PATH} projectName=${PROJECT_NAME} webRootPath=/opt/devroot/" deploy_test: stage: deploy_test environment: name: test only: - master script: - rsync -av . ${WORKSPACE_PATH} && cd ${WORKSPACE_PATH} - npm run build:stage - ansible-playbook ansible-deploy.yml --extra-vars "hosts=cloud_ui_test projectDir=${WORKSPACE_DIST_PATH} projectName=${PROJECT_NAME} webRootPath=${WEB_ROOT_PATH}" deploy_production: stage: deploy_production only: - production when: manual script: - npm install - npm run build:prod - ansible-playbook ansible-deploy.yml --extra-vars "hosts=dvs-front-prod projectDir=${PROJECT_PATH} projectName=xianglin-cloud-ui/${PROJECT_NAME} webRootPath=/opt/" 上面这个.yml文件,我们首先定义了三个阶段,deploy_dev部署到dev环境,并且拉去的是dev分支代码; deploy_test部署到test环境,拉去的是master分支代码,deploy_production拉取production分支代码,执行完后需要手动操作 ...

August 7, 2025

包管理

现如今,前端开发的同学已经离不开 npm 这个包管理工具,其优秀的包版本管理机制承载了整个繁荣发展的NodeJS社区,理解其内部机制非常有利于加深我们对模块开发的理解、各项前端工程化的配置以加快我们排查问题(相信不少同学收到过各种依赖问题的困扰)的速度。 本文从三个角度:package.json、版本管理、依赖安装结合具体实例对 npm 的包管理机制进行了详细分析。 一、剖析 package.json 在 Node.js 中,模块是一个库或框架,也是一个 Node.js 项目。Node.js 项目遵循模块化的架构,当我们创建了一个 Node.js 项目,意味着创建了一个模块,这个模块必须有一个描述文件,即 package.json。它是我们最常见的配置文件,但是它里面的配置你真的有详细了解过吗?配置一个合理的 package.json 文件直接决定着我们项目的质量,所以首先带大家分析下 package.json 的各项详细配置。 1.1 必备属性 package.json 中有非常多的属性,其中必须填写的只有两个:name 和 version ,这两个属性组成一个 npm 模块的唯一标识。 npm包命名规则 name 即模块名称,其命名时需要遵循官方的一些规范和建议: 包名会成为模块url、命令行中的一个参数或者一个文件夹名称,任何非url安全的字符在包名中都不能使用,可以使用 validate-npm-package-name 包来检测包名是否合法。 语义化包名,可以帮助开发者更快的找到需要的包,并且避免意外获取错误的包。 若包名称中存在一些符号,将符号去除后不得与现有包名重复 例如:由于react-native已经存在,react.native、reactnative都不可以再创建。 如果你的包名与现有的包名太相近导致你不能发布这个包,那么推荐将这个包发布到你的作用域下。 例如:用户名 conard,那么作用域为 @conard,发布的包可以是@conard/react。 查看包是否被占用 name 是一个包的唯一标识,不得和其他包名重复,我们可以执行 npm view packageName 查看包是否被占用,并可以查看它的一些基本信息: 若包名称从未被使用过,则会抛出 404 错误: 另外,你还可以去 https://www.npmjs.com/ 查询更多更详细的包信息。 1.2描述信息 基本描述 { "description": "An enterprise-class UI design language and React components implementation", "keywords": [ "ant", "component", "components", "design", "framework", "frontend", "react", "react-component", "ui" ] } description用于添加模块的的描述信息,方便别人了解你的模块。 ...

August 7, 2025

版本控制

什么是版本控制 版本控制是一种系统性的方法,用于管理和跟踪软件开发项目中的代码、文件和资源。它的核心目标是在不同时间点的开发中保持代码的一致性、可追溯性和可维护性。 为什么要做版本控制 这个问题可以从定义中的关键词可以看出端倪:不同时间点下的代码一致性、可追溯性、可维护性。这里额外补充一点:当今的互联网开发早已步入敏捷迭代,各种动态化方案层出不穷都有着顺应时代潮流的意思,因此软件开发一定会越来越多地涉及到团队开发,团队之间的协作也一定需要这样一个机制去保障稳定性。 代码一致性 版本控制系统允许开发人员有效地管理和组织项目中的代码。它跟踪每个文件的历史记录,记录了每个版本的变更,包括谁做了什么修改、何时以及为什么。 可追溯性 版本控制系统允许为重要的里程碑或发布创建标签(tag),以便轻松识别和回滚到特定版本。 并且当某个版本的代码出现问题,版本控制系统允许开发人员轻松地回滚到之前的稳定版本,并在不破坏其余代码的情况下进行修复。 可维护性 开发人员可以创建分支(branch),这是一个独立的代码副本,用于开发新功能、修复错误或进行实验性工作。然后,这些分支可以与主代码库合并,以实现功能集成。 团队协作 在多人协作的环境中,不同开发者可能同时修改代码。版本控制系统确保不同的修改不会互相冲突,以及如何解决冲突。 版本控制系统促进了团队之间的协作,可以通过代码审查工具进行代码审查,以确保代码质量和一致性。 版本控制工具Git Git的安装 WIndows 访问 Git官网,选择对应的操作系统下载即可。 这里下载独立安装包即可 点install就行,会自动安装一个git GUI工具,但通常不会直接用它 下载完成安装即可,输入以下命令验证安装是否成功 git version 图形化工具 安装 这里推荐TortoiseGit这个工具,非常方便。但我觉得Git本身学习成本并不高,GUI工具更多是为了提升效率,基本的Git知识点还是需要掌握的。 下载本体 然后是语言包 安装时一路next即可,安装完本体后再安装语言包。 配置好Github的账户、邮箱、秘钥等,注意配置好了密钥也不会在下述部分显示,可以打开编辑全局进行查看 使用 在使用之前先提前补充一个点(后续会详细介绍),Git有本地仓库和远端仓库的概念,通常支持HTTPS和SSH两种协议进行关联,两种方式优缺点也十分明显: HTTPS:无需额外配置,但是在每次连接远端仓库时需要输入账号密码进行校验。 SSH:生成一组密钥对,需要本地进行配置,好处是配置好后每次连接远端仓库时会自动比较密钥对,无需额外校验。关于SSH详细可以参考这篇文章ssh-远程登录协议 为了一劳永逸,我们这里配置下SSH,首先打开Git bash # 输入下列命令,最后一个参数是您的邮箱 $ ssh-keygen -t rsa -C yourEmail.com 可以看到密钥对已经生成了 进入上述目录,注意.ssh文件夹默认是隐藏的,直接访问路径就行 以Github为例,配置公钥 创建一个Demo项目,平时学习的话就创建公有项目就行 复制一下我们的项目链接 选取一个你喜欢的目录作为本地工作区,然后按鼠标右键 ...

August 7, 2025

代码规范工程化

规范化是前端工程化的一个重要部分。现在,有许多工具能够辅助我们实行代码的规范化,比如你一定知道的 ESLint 和 Prettier。 今天,来聊聊这些工具的工作原理和基本使用,了解它们是如何发挥作用的,以及如何更好地利用这些工具去规范项目的代码。 1. ESlint - 检查你的 JavaScript 代码 让我们先从知名度最高的 ESLint 开始。 1.1. ESLint 及其作用 Lint 是一类专门用于检查代码的工具软件, 也称 linter。ESLint ,即 JavaScript (ECMAScript)代码的检查工具。 正如官网的介绍 —— “Find and fix problems in your JavaScript code”,ESLint 能够辅助查找出你的 JavaScript 代码中的问题,包括: 代码风格问题(styles)。比如,运算符两边的空格、语句末尾的分号。 不好的写法。比如,使用 == 进行比较而不是 ===。 可能存在逻辑问题的代码模式。比如,定义了一个变量,但没有使用到它。 此外,ESLint 还能够帮你自动修复一些简单的问题。 我们将在下一小结学习如何使用 ESLint 检查我们的 JavaScript 代码,并修复其中的一些问题。 1.2. ESLint 快速上手 为了在项目中使用 ESLint,需要先安装它。 # 初始化一个 npm 项目 mkdir eslint-test cd eslint-test npm init -y # 安装 eslint npm init @eslint/config 回答一系列问题后,你可以看目录中的配置文件 .eslintrc.js,这个配置文件告诉 ESLint 如何去解析项目,这个项目采用了哪些规范和规则。 ...

August 7, 2025

前端任务自动化(npm scripts实现)

在前端开发中,我们经常需要执行各种重复性任务,比如编译代码、启动本地服务器、监听文件变化、运行测试、部署代码等。虽然可以使用不同的工具来完成这些任务,但npm本身提供了一个强大的功能:npm scripts。通过npm scripts,我们可以将这些任务脚本化并集中管理,从而提高开发效率。 本文将专注于如何使用npm scripts自动化前端开发任务,以及它在项目管理中的作用。 什么是npm scripts? npm scripts是npm提供的一种机制,允许我们在项目的package.json文件中定义一组命令,方便执行各种任务。在package.json文件中,scripts字段可以包含多个脚本,每个脚本都是一个键值对,其中键是脚本的名称,值是实际要执行的命令。例如: { "scripts": { "build": "webpack --config webpack.config.js", "test": "jest" } } 在上面的示例中,定义了两个脚本:build 和 test。可以通过以下方式在命令行中运行这些脚本: npm run build npm run test npm会执行对应的命令,自动帮我们完成编译和测试任务。 为什么使用npm scripts? 在现代前端开发流程中,自动化任务是不可或缺的,而npm scripts的使用有以下几大优势: 减少对全局依赖的需求:在传统项目中,开发者通常需要全局安装各种命令行工具,如webpack、eslint、babel等。而npm scripts可以直接调用项目的本地依赖,无需全局安装。这确保了不同开发环境中的工具版本一致,减少了版本冲突和兼容性问题。 集中管理任务:npm scripts将所有任务定义集中在package.json文件中,项目中的所有开发人员都可以直观地看到可用的任务列表,无需了解每个工具的详细使用方法。只需运行npm run <task>,便可一键完成常见任务。 支持跨平台执行:npm scripts中的命令可以跨平台使用,在Windows、macOS和Linux系统上表现一致,尤其适合团队协作。 便于组合和链式执行:npm允许通过逻辑操作符来组合脚本,实现复杂任务的自动化执行。例如可以通过&&和||来串联多个任务,使得多个步骤一次完成。 如何定义和使用npm scripts? 让我们通过具体的示例来看看npm scripts如何自动化前端开发任务。 1. 设置基本的开发任务 假设我们正在开发一个React项目,我们可以在package.json中定义以下基本任务: { "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", "test": "jest", "lint": "eslint src/**/*.js" } } 这里的脚本分别执行以下任务: ...

August 7, 2025

前端构建工具详解

谈到构建工具,大家首先想到的肯定就是 Webpack 以及现在最🔥的 Vite。 Webpack,功能强大,生态丰富,从面世到今天,一直是很受大家欢迎;Vite 采用 unbundle 构建模式,带来了极致的开发体验,给开发人员以新的选择。 在这两个构建工具之外,还有其他的构建工具,如和 Webpack、Vite 类似的 Rollup、Parcel、Esbuild,自动化构建工具 grunt、gulp,以及更加久远的 YUI Tool。 这些工具的存在,构成了前端构建工具的发展史。 YUI Tool + Ant YUI tool 是 07 年左右出现的一个构建工具,功能比较简单,用于压缩混淆 css 和 js 代码,需要配合 java 的 Ant 使用。 当时 web 应用开发主要采用 JSP,还不像现在这样前后端分离,通常是由 java 开发人员来编写 js、css 代码,前端代码都是和后端 java 代码放在一起的。因此前端代码的压缩混淆也就基于 java 实现了。 Grunt / Gulp Grunt / Gulp 都是运行在 node 环境上的自动化工具。 在开发过程中,我们可以将一些常见操作如解析 html、es6 代码转换为 es5、less / sass 代码转换为 css 代码、代码检查、代码压缩、代码混淆配置成一系列任务,然后通过 Grunt / Gulp 自动执行这些任务。 Grunt 和 Gulp 的不同点: ...

August 7, 2025

美团-零售-秋招 · 第 1 轮 · 一面

← 已是第一轮 · 返回本次面经 · 已是最后一轮 → 本轮要点: 本次面试主要考察前端基础知识,包括数据结构、网络协议、操作系统、JavaScript、CSS、React以及算法等方面。 本轮共 12 道题。答案默认折叠,便于先自行作答。 1. 数组和链表的区别,读取 和 删除 的时间复杂度 题目要点 数据结构基础:考察对数组和链表这两种基本数据结构概念的理解,包括它们的存储方式和特点。 时间复杂度分析:评估数据结构在不同操作下的性能表现,这是衡量算法效率的关键指标。 参考答案 1.1 原理说明 数组:数组是一种将元素存储在连续内存空间中的数据结构。它通过索引来直接访问任意元素,因为所有元素的大小相同且物理地址连续,可以通过首地址和索引计算出元素的确切位置。 链表:链表是一种将元素存储在非连续内存空间中的数据结构。每个元素(称为节点)包含数据本身以及指向下一个元素的指针(或引用)。节点之间通过这些指针逻辑上连接起来,但不要求物理上连续。 联系与区别:数组和链表都是线性数据结构,用于有序地存储和组织数据。它们的主要区别在于内存的物理存储方式和由此带来的对元素访问、插入、删除操作的效率差异。数组支持随机访问,而链表只能顺序访问。 为什么会出现这个技术需求或问题:不同的数据存储和操作场景对数据结构有不同的性能要求。当需要快速随机访问数据时,数组由于其连续存储特性表现更优;而当需要频繁地进行插入和删除操作时,链表由于其灵活的指针连接方式,避免了大量元素移动,因此效率更高。 1.2 核心用法 + 示例代码 读取操作: 数组:通过索引直接访问元素,时间复杂度为 O(1)。无论数组大小,访问任何元素所需时间都是恒定的。 const arr = [10, 20, 30, 40, 50]; console.log(arr[2]); // 输出 30,直接访问,时间复杂度 O(1) 链表:必须从链表的头部节点开始,沿着指针逐个遍历,直到找到目标元素。因此,时间复杂度为 O(n),其中 n 是链表的长度。在最坏情况下,需要遍历整个链表。 class Node { constructor(val) { this.val = val; this.next = null; } } const head = new Node(10); head.next = new Node(20); head.next.next = new Node(30); let current = head; while (current && current.val !== 30) { current = current.next; } console.log(current ? current.val : 'Not found'); // 输出 30,需要遍历,时间复杂度 O(n) 删除操作: 数组:删除数组中的某个元素后,为了保持内存的连续性,其后的所有元素都需要向前移动以填补空缺。这个移动操作的时间复杂度为 O(n)。 const arr = [1, 2, 3, 4, 5]; arr.splice(2, 1); // 删除索引为2的元素(即3),后续元素(4, 5)前移,时间复杂度 O(n) console.log(arr); // 输出 [1, 2, 4, 5] 链表:如果已知要删除节点的前一个节点,删除操作只需要修改前一个节点的指针,使其指向被删除节点的下一个节点。这个操作的时间复杂度为 O(1)。如果需要先查找再删除,则总时间复杂度为 O(n)(查找的开销)。 // 假设我们有一个链表 1 -> 2 -> 3,我们要删除值为 2 的节点 // 模拟找到值为 1 的节点 (prevNode) 和值为 2 的节点 (nodeToDelete) let headDelete = new Node(1); let node2 = new Node(2); let node3 = new Node(3); headDelete.next = node2; node2.next = node3; let prevNode = headDelete; // 值为 1 的节点 let nodeToDelete = node2; // 值为 2 的节点 prevNode.next = nodeToDelete.next; // 将 1 的 next 指向 3,时间复杂度 O(1) // 现在链表变为 1 -> 3 优势总结: ...

August 4, 2025