2025年工程化面试高频考点

38 阅读21分钟

前端模块化


说说你对前端工程化的理解?为什么要工程化

一般的前端工程化就是:模块化拆分 + 规范化开发 + 自动化构建/测试/部署

维度具体表现
模块化代码按功能拆分(如 ES Module、组件库),降低耦合度
规范化统一代码风格(ESLint)、提交规范(Commitizen)、目录结构
自动化构建(Webpack)、测试(Jest)、部署(CI/CD)等流程无需手动干预

原因

指标工程化影响
提升效率减少重复劳动(封装组件)
提高质量静态检查 + 自动化测试提前拦截 Bug,类型系统减少运行时错误
提升性能构建优化(Chunk 拆分、按需加载)、资源压缩(CSS/JS minify)、缓存策略
总结 :前端工程化本质是 用工具约束人,用流程保证质量

什么是模块?

模块(Module)是将程序代码按功能、逻辑拆分后的独立单元,每个模块封装了特定的功能(变量、函数、类、逻辑等),同时通过明确的规则暴露需要对外提供的内容隐藏内部实现细节,是前端工程化中 “分而治之” 思想的核心体现。

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

 模块化的好处特点?为什么要模块化?

  1. 避免命名冲突(独立性):模块内部的变量、函数不会污染全局作用域(解决传统 JS “全局变量冲突” 问题);
  2. 高可复用性:一个模块可在项目多个地方引用(比如封装的 “日期格式化模块”,可在登录、订单页面复用);
  3. 高可维护性:功能拆分后,修改某个模块(如调整购物车逻辑)不会影响其他模块,便于迭代和 bug 修复;
  4. 更好的分离,按需引入:可只加载需要的模块(比如首屏只加载 “首页模块”,而非整个项目代码),优化性能。

模块化的进化过程

  1. 无模块化 →
  2. 手写隔离(IIFE / 命名空间) →
  3. 规范萌芽(CommonJS --> AMD --> CMD) →
  4. 工程化打包(Webpack) →
  5. 原生支持(ES6 Module)

核心目标始终是:让代码更易拆分、复用、维护,适配从「小脚本」到「大型应用」的开发需求

1. 早期无模块化(2000 年前后,JS 刚普及)
  • 现状:没有任何模块化概念,所有代码写在<script>标签里,全局变量满天飞。比如:多个<script>里的var a = 1会互相覆盖,函数名也容易冲突;引入顺序错了(比如先调用 jQuery 再引入 jQuery 文件)就报错。
  • 临时方案:用「命名空间」(比如var myApp = { util: {}, api: {} })或「立即执行函数(IIFE)」封装代码,勉强隔离作用域,但依赖管理还是靠手动调整<script>顺序,复杂项目完全扛不住。
2. 模块化规范萌芽(2010 年左右,Node.js/ 复杂前端项目兴起)
  • 背景:Node.js 诞生(2009),前端项目规模变大(如电商、后台系统),急需统一的模块化规则。

  • 核心规范

    • CommonJS:Node.js 的默认规范,用require引入、module.exports导出,同步加载(适合服务端,因为文件读本地),但前端用不了(浏览器加载文件是异步的,同步require会阻塞页面)。
    • AMD:专门为浏览器设计的异步模块化规范(代表:RequireJS),用define定义模块、require加载,解决异步依赖问题,但语法繁琐(比如嵌套多层依赖),学习成本高。
    • CMD:阿里 SeaJS 提出的折中方案,兼容同步 / 异步,语法更简洁,但生态不如 AMD,后来慢慢淡出。
3. 工程化工具适配(2015 年后,Webpack 等构建工具普及)
  • 背景:CommonJS/AMD 语法不统一,浏览器原生不支持,构建工具(Webpack、Rollup)出现,解决「模块化语法兼容」问题。
  • 核心逻辑:开发时用 CommonJS/ES6 Module 写代码,构建工具把所有模块打包成浏览器能识别的「全局变量形式」的单文件,既保留模块化优势,又兼容浏览器。
  • 关键:Webpack 的出现让前端模块化落地成本大幅降低,成为行业标配。
4. 原生模块化支持(2017 年后,ES6 Module 普及)
  • 核心:ES6 在语言层面定义了import/export模块化规范,浏览器逐步原生支持(通过<script type="module">),Node.js 也在 v14 后全面支持。
  • 变化:无需构建工具也能直接用模块化(简单项目),复杂项目仍用 Webpack/Vite 优化(按需加载、Tree-Shaking 等);模块化从「工具层面」回归「语言层面」,成为前端开发的基础。

总结

  • CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
  • AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
  • CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
  • ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案

CommonJS、AMD、CMD、UMD、ES Module 区别是什么?

方案适用场景核心特点现状
CommonJSNode.js 服务端同步、运行时加载、值拷贝仍为 Node.js 主流(暂未完全被 ESM 取代)
AMD浏览器端异步、依赖前置(提前加载,预先加载了所有依赖不管你是否用到)基本淘汰
CMD浏览器端异步、就近依赖(按需加载,用到了再引用依赖)已淘汰
UMD跨环境兼容方案自适应、 兼容 CommonJS/AMD/ 全局已淘汰
ES Module全场景(端 + 服务)静态、异步、值引用现代前端 / Node.js 主流

AMD 与 CMD 的核心区别

‌AMD(异步模块定义)和 CMD(通用模块定义)是前端模块化规范,均用于解决 JavaScript 代码的依赖管理和异步加载问题。两者均支持异步加载模块,AMD 侧重预加载依赖,CMD 侧重按需加载,目前因 ES6 Module 的普及,两者使用场景已减少

对比维度AMD(RequireJS)CMD(SeaJS)
依赖加载策略依赖前置:声明模块时提前列出所有依赖,加载器先加载全部依赖再执行模块就近依赖:无需提前声明,代码用到时才 require 加载(按需加载)
模块执行时机依赖加载完成后立即执行对应模块所有依赖加载完成后,在代码执行到对应位置时才执行模块
语法风格语法冗余,强调依赖前置声明贴近 CommonJS,语法简洁,更符合同步书写习惯
核心设计理念优先保证依赖全加载,执行顺序可控按需加载,减少不必要的提前加载,更灵活
代表实现RequireJSSeaJS(已停更)

SeaJS 已停止维护,AMD/CMD 逐渐被 ES Module 取代

CommonJS与ES6 Module的区别 🌟🌟

对比维度CommonJSES6 Module (ESM)
语法差异require 导入、module.exports/exports 导出import 导入、export 导出
加载机制运行时加载(执行到 require 才加载,运行时确定依赖关系)编译时静态分析(编译阶段确定依赖)
导出机制导出「值拷贝」,浅拷贝:原始值拷贝后独立,引用类型共享地址,后续修改不影响导入方导出「只读的变量映射」,导入方同步更新
树摇支持无树摇(无法剔除未使用代码)支持树摇(静态分析实现代码优化)
运行环境Node.js 原生支持,浏览器需打包(借助webpack等工具)浏览器 / Node.js 均原生支持(Node 需指定 type: module)
顶层作用域顶层 this 指向 module.exports顶层 this 为 undefined(严格模式
模块缓存缓存模块,会缓存运行结果,导出的值副本缓存模块本身,并且不会缓存运行结果,导出只读的变量映射,引用始终指向模块内部

说明文字

  • JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,在根据引用到被加载的那个模块里面去取值。ES6 模块是动态引用,并且不会缓存运行结果

  • ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。

  • ES6 Module通过其静态特性可以进行编译过程中的优化,并且具备处理循环依赖的能力

理解UMD的概念,以及它如何使模块在不同环境下(如浏览器和Node.js)都能正常工作。

一、UMD 核心定义

UMD(Universal Module Definition)是跨环境的模块化规范,不是全新规范,而是封装了 CommonJS、AMD、浏览器全局变量等多种模块方式,让同一模块能在 Node.js、浏览器(含 AMD 加载器)、无模块加载器的全局环境下都能运行。

二、核心实现逻辑(适配不同环境)

UMD 通过环境检测判断当前运行环境,选择对应模块方式:

  1. Node.js 环境(CommonJS) :检测 module.exports/require,按 CommonJS 导出模块;
  2. 浏览器 AMD 环境:检测 define.amd,按 AMD 的 define 方式定义模块;
  3. 浏览器全局环境:无模块加载器时,将模块挂载到 window 全局对象上。

三、简化示例(核心逻辑)

(function (root, factory) {
  if (typeof module === 'object' && module.exports) {
    // Node.js/CommonJS 环境
    module.exports = factory(require('依赖'));
  } else if (typeof define === 'function' && define.amd) {
    // 浏览器 AMD(RequireJS)
    define(['依赖'], factory);
  } else {
    // 浏览器全局环境
    root.模块名 = factory(root.依赖);
  }
}(this, function (依赖) {
  // 模块核心逻辑
  return { /* 导出内容 */ };
}));

四、关键特点

  1. 兼容性一站式适配 CommonJS、AMD、浏览器全局,无需为不同环境写多版代码;
  2. 无侵入:仅通过环境判断切换导出方式,不改变模块核心逻辑;
  3. 局限性:代码体积略增(多了环境判断),循环依赖处理需适配底层规范(如 Node 侧按 CommonJS,浏览器侧按 AMD)。

模块的动态导入:了解如何使用import()进行模块的动态加载,以及它在代码分割和懒加载中的应用

模块动态导入(import ())核心解析

1. 什么是 import ()

import() 是 ES Module 补充的动态加载语法突破了 ESM 静态编译的限制,允许在运行时按需加载模块;它返回一个 Promise,加载成功后解析为模块对象,失败则触发 reject。

2. 基本使用方式
// 基础用法:加载单个模块
import('./utils.js')
  .then((module) => {
    // module 是完整模块对象,包含所有导出
    console.log(module.default); // 访问默认导出
    console.log(module.format); // 访问命名导出
  })
  .catch((err) => console.error('加载失败:', err));

// 结合 async/await(更简洁)
async function loadModule() {
  try {
    const utils = await import('./utils.js');
    utils.doSomething();
  } catch (err) {
    // 处理加载错误
  }
}
3. 核心应用场景
(1)代码分割(Code Splitting)

打包工具(Webpack/Rollup/Vite)会识别 import(),将动态加载的模块拆分为独立的 chunk(代码块),而非打包进主文件,减小首屏加载体积

// 主文件 chunk:仅加载核心逻辑
// 点击按钮时才加载「编辑器模块」,拆分为独立 chunk
document.getElementById('editor-btn').addEventListener('click', async () => {
  const Editor = await import('./editor.js');
  Editor.init();
});
(2)懒加载(Lazy Loading)
  • 路由懒加载(前端框架核心场景):Vue/React 中按需加载路由组件,只有访问对应路由时才加载组件代
  • 组件 / 功能懒加载:非核心功能(如弹窗、图表、富文本)仅在用户触发操作时加载,避免首屏加载冗余代码。
(3)动态路径加载

根据运行时条件(如用户权限、环境变量)加载不同模块,适配灵活的业务场景:

// 根据环境加载不同配置
async function loadConfig(env) {
  const config = await import(`./config.${env}.js`);
  return config.default;
}
// 生产环境加载 prod 配置,开发环境加载 dev 配置
loadConfig(process.env.NODE_ENV);
4. 关键特点
  • 异步非阻塞:加载过程不阻塞主线程,适配浏览器端性能要求;
  • 兼容 ESM 特性:加载后的模块遵循 ESM 动态引用规则;
  • 打包友好:被主流构建工具识别,支持 chunk 拆分和缓存优化;
  • 全局可用:浏览器、Node.js 均支持(Node.js 14+ 无需额外配置)。
总结

import() 解决了 ESM 静态加载的灵活性不足问题,核心价值是按需加载—— 通过代码分割和懒加载减少首屏体积、提升加载速度,是现代前端性能优化的核心手段之一,尤其适用于大型应用的路由、组件、功能模块拆分。

模块化开发中如何处理循环依赖的问题,以及不同模块化规范下的解决方案

一、循环依赖核心

模块间互相引用,导致加载 / 执行顺序冲突,引发未定义、加载失败等问题。

二、通用思路

  1. 延迟引用:不在初始化阶段用依赖,在函数 / 执行阶段获取;
  2. 解耦拆分:抽离公共逻辑,减少交叉依赖;
  3. 利用各规范的模块执行特性。

三、各规范解决方案

规范核心解决方式
CommonJS延迟引用(函数内用依赖)+ 提前导出被依赖属性
ESM导出引用型值(let/const)+ 动态 import () + 拆分模块
AMD异步回调中延迟引用,避免初始化时调用依赖
UMD适配底层规范(CommonJS/AMD)+ 内聚循环依赖逻辑

四、最佳实践

优先拆分模块从根源避免循环依赖;无法避免时,按对应规范延迟引用 / 动态加载,避开初始化阶段使用依赖。

模块化开发最佳实践

核心目标:降复杂度、提可维护性

一、核心设计原则

  1. 单一职责
  • 定义:一个模块只负责完成一个独立的功能 / 业务逻辑,避免 “万能模块”。

  • 落地

    • 按功能拆分:如将 “用户管理” 拆分为「用户数据请求」「用户信息渲染」「用户权限校验」3 个模块,而非混在一个文件中;
    • 按职责分层通用工具(utils)、业务逻辑(service)、UI 组件(components)严格分离,工具模块不掺杂业务逻辑;
    • 反例:一个模块既处理接口请求,又操作 DOM,还包含常量定义,修改一处牵一发而动全身。
  1. 松耦合 + 高内聚
  • 松耦合:模块间依赖尽可能少,且依赖通过 “接口”(导出的方法 / 变量)交互,而非直接修改对方内部状态;

    • 落地:使用 “依赖注入” 替代模块内部硬编码依赖(如将请求实例传入业务模块,而非模块内直接引入 axios);通过导出纯函数(无副作用)降低模块间的状态依赖。
  • 高内聚:模块内部的逻辑高度相关,无关代码不混入(如 “日期处理工具” 模块不包含字符串格式化逻辑)。

  1. 封闭 - 开放

    • 对外暴露稳定接口,内部逻辑可修改不影响外部;新增功能扩展接口而非修改原有逻辑。

二、落地实践(从编码到工程化)

1. 模块拆分与命名规范

  • 拆分维度

    • 按业务域:如电商项目拆分为「商品模块」「订单模块」「支付模块」,模块间通过公共接口交互;
    • 按复用性:通用能力(如请求封装、日期工具)抽为独立模块,业务模块依赖通用模块,而非重复编写;
  • 命名规范

    • 语义化命名:如 user-api.js(用户接口)、date-utils.js(日期工具),避免 utils.jsfunc.js 这类模糊命名;

    • 目录结构清晰:

      src/
      ├── utils/        // 通用工具(纯函数、无业务依赖)
      ├── api/          // 接口请求(仅封装请求,无业务逻辑)
      ├── business/     // 业务逻辑(依赖api和utils,不直接操作UI)
      ├── components/   // UI组件(仅负责渲染,依赖业务逻辑数据)
      └── config/       // 配置项(全局常量、环境变量)
      

2. 依赖管理最佳实践

  • 最小化依赖:仅引入当前模块必需的依赖,避免 “全量导入”(如 import _ from 'lodash' 改为 import { debounce } from 'lodash');

  • 避免循环依赖

    • 根源解决:拆分公共逻辑为独立模块(如 A 和 B 循环依赖,抽离公共逻辑为 C,A/B 均依赖 C);
    • 应急方案:延迟引用(函数内动态导入)、依赖注入,而非在模块顶层直接引用循环依赖;
  • 统一依赖版本:通过 lock 文件(package-lock.json/yarn.lock)固定依赖版本,避免不同环境依赖不一致;

  • 区分依赖类型:开发依赖(devDependencies)和生产依赖(dependencies)严格区分(如 webpack、eslint 归为开发依赖)。

3. 模块导出 / 导入规范

  • 导出精简:仅暴露外部需要的接口,内部变量 / 函数用 private(ES 模块)或闭包隐藏;

    • 反例:模块内定义 10 个函数,全部 export,外部仅需 1 个,其余均为冗余暴露;
  • 导入明确

    • 避免通配符导入(import * as utils from './utils'),改为按需导入(import { formatDate } from './utils'),提升可读性和打包体积;
    • 绝对路径导入:配置别名(如 webpack 的 alias),将 import '../../utils' 改为 import '@/utils',避免路径层级混乱;
  • ESM 优先:现代项目优先使用 ES 模块(import/export),而非 CommonJS(require),利用静态分析优势(如 tree-shaking、类型检查)。

4. 工程化保障

  • 模块边界校验:通过 TypeScript 定义接口类型,约束模块输入 / 输出,避免类型不匹配;

  • 按需加载

    • 前端:利用 ES 模块动态导入(import())实现路由 / 组件懒加载,减少首屏体积;
    • 后端:Node.js 中通过 require() 动态加载非核心模块(如按需加载插件);
  • 模块测试:每个模块独立编写单元测试,确保修改模块不影响其他功能(如 Jest 测试工具);

  • 文档化:核心模块添加 README 或注释,说明模块功能、导出接口、依赖关系,降低协作成本。

三、避坑指南(常见问题与解决方案)

常见问题解决方案
模块粒度过粗(万能模块)按单一职责拆分,拆到 “修改时无需考虑其他逻辑”
模块粒度过细(碎片化)合并高频复用的小模块,避免 “文件爆炸”
硬编码依赖依赖注入 + 配置化,降低模块间耦合
循环依赖拆分公共逻辑 + 延迟引用
冗余导出 / 导入按需导出 / 导入 + 定期清理未使用的依赖

单一职责拆分,以松耦合交互,以工程化保障,实现模块 “可理解、可复用、可测试、可替换”。

任务自动化


常见考点

  1. 任务自动化的概念:理解任务自动化的定义、目的和重要性。

  2. npm scripts基础

    • 如何在package.json中定义npm脚本。
    • 使用npm run <script-name>执行脚本的方法。
    • npm脚本的生命周期钩子(如prepost)及其用法。
  3. npm scripts进阶

    • 如何在npm脚本中使用环境变量。
    • 如何通过npm脚本调用其他命令行工具或脚本。
    • npm脚本的并行和串行执行策略。
  4. 其他任务自动化工具

    • 列举并比较Gulp、Grunt、Make等任务自动化工具的特点和适用场景。
    • 了解这些工具的基本配置和使用方法。
  5. 任务自动化的实践

    • 如何根据项目需求选择合适的任务自动化工具。
    • 如何编写和维护清晰、可复用的任务自动化脚本。
    • 如何将任务自动化脚本集成到持续集成/持续部署(CI/CD)流程中。
  6. 性能优化

    • 如何通过任务自动化工具优化构建和部署过程。
    • 如何利用缓存、并行处理等技术提高任务执行效率。

前端任务自动化全解析(核心问答版)

一、任务自动化的核心概念

1. 定义

任务自动化是通过脚本 / 工具替代人工执行重复、标准化的开发 / 部署任务(如代码编译、打包、测试、代码检查等)的过程。

2. 目的 & 重要性

  • 减少人工操作,降低出错率
  • 统一团队执行标准,避免 “本地能跑、线上报错”;
  • 提升研发效率,聚焦核心业务开发;
  • 为 CI/CD 提供基础,支撑自动化部署。

二、npm scripts 核心用法

1. 基础使用

(1)定义脚本

package.jsonscripts 段中声明,支持任意命令行指令:

{
  "scripts": {
    "dev": "vite", // 启动开发服务
    "build": "vite build", // 生产打包
    "test": "jest" // 执行测试
  }
}
(2)执行脚本
npm run dev # 执行dev脚本
npm run build # 执行build脚本
# 简写:npm test / npm start(对应test/start脚本)
(3)生命周期钩子

脚本名以 pre/post为前缀,执行主脚本时自动触发:

{
  "scripts": {
    "prebuild": "eslint src", // 执行build前先做代码检查
    "build": "vite build",
    "postbuild": "echo '打包完成'" // build执行后输出提示
  }
}
# 执行npm run build时,顺序:prebuild → build → postbuild

2. 进阶用法

(1)使用环境变量
  • 方式 1:直接在脚本中拼接(跨平台需借助cross-env):

    {
      "scripts": {
        "dev": "cross-env NODE_ENV=development vite", // 跨平台设置环境变量
        "build": "cross-env NODE_ENV=production vite build"
      }
    }
    
  • 方式 2:读取.env文件(需dotenv):

    {
      "scripts": {
        "dev": "node -r dotenv/config vite"
      }
    }
    
(2)调用其他工具 / 脚本

npm 脚本可直接调用node_modules/.bin下的工具(无需全局安装),也可执行自定义 JS/Shell 脚本:

{
  "scripts": {
    "lint": "eslint src --fix", // 调用eslint
    "clean": "rm -rf dist", // 执行Shell命令(Windows需替换为rd/s/q dist)
    "custom": "node scripts/custom.js" // 执行自定义JS脚本
  }
}
(3)并行 / 串行执行
  • 串行(按顺序执行):用&&连接

    "scripts": {
      "deploy": "npm run build && scp dist/* server:/var/www" // 先打包再部署
    }
    
  • 并行(同时执行):&(Windows 不兼容)npm-run-all

    {
      "scripts": {
        "dev:all": "npm-run-all --parallel dev:vite dev:mock", // 并行启动开发服务和mock
        "dev:vite": "vite",
        "dev:mock": "mock-server"
      }
    }
    

三、主流任务自动化工具对比

工具核心特点执行方式适用场景配置复杂度
npm scripts无额外依赖,基于命令行命令行指令中小型项目、简单任务(打包 / 测试)
Gulp基于流(Stream),轻量代码定义任务前端工程化(编译 / 压缩 / 监听文件)
Grunt基于配置,插件生态丰富配置文件定义传统前端项目、复杂配置型任务
Make跨平台(偏后端),依赖 Makefile声明式规则后端 / 全栈项目、编译型任务

四、任务自动化实践

1. 工具选择原则

  • 中小型前端项目:优先用npm scripts(无额外依赖,维护成本低);
  • 复杂前端工程化(如多文件监听、流式处理):选 Gulp;
  • 传统 / 后端项目:选 Make/Grunt;
  • 跨团队 / 跨技术栈项目:优先标准化工具(如 npm scripts + 少量 Gulp)。

2. 脚本编写 & 维护规范

  • 拆分粒度:单个脚本只做一件事(如lint/build/deploy分离);
  • 复用性:抽离通用逻辑到单独 JS/Shell 脚本(如scripts/utils.js);
  • 注释:复杂脚本添加说明(如环境变量含义、依赖条件);
  • 跨平台:避免平台特定命令(用cross-env/rimraf替代rm/set)。

3. 集成 CI/CD(以 GitHub Actions 为例)

将自动化脚本嵌入 CI/CD 流程,实现 “提交代码自动构建 / 测试 / 部署”:

# .github/workflows/ci.yml
name: CI/CD
on: [push]
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: 安装依赖
        run: npm install
      - name: 代码检查&打包
        run: npm run lint && npm run build # 执行npm脚本
      - name: 部署到服务器
        run: npm run deploy # 执行自定义部署脚本

五、性能优化技巧

1. 构建 / 部署流程优化

  • 增量构建:只处理变更文件(如 Gulp 的gulp-watch、Vite 的依赖预构建缓存);
  • 缓存复用:缓存依赖安装结果(如 CI/CD 中缓存node_modules)、构建产物(如 Vite 的node_modules/.vite);
  • 任务拆分:将耗时任务拆分为并行子任务(如linttest并行执行);
  • 资源压缩:构建阶段压缩代码 / 图片(如terser/imagemin),减少传输耗时。

2. 执行效率提升

  • 并行处理:用npm-run-all/Gulp 的parallel并行执行无依赖的任务;
  • 跳过冗余步骤:通过条件判断跳过无需执行的任务(如仅变更文档时跳过 build);
  • 轻量工具:替换重型工具(如用esbuild替代babel做快速编译)。

关联面试题

npm script 了解多少?

npm 是什么?

npm script 生命周期有哪些?

npm lock 文件了解多少?

说说你对 pnpm 的了解

npm 中的“幽灵依赖”是什么?

npm 和 yarn有哪些不一样的地方?

相比于npm和yarn,pnpm的优势是什么?

说说你对 npm 包管理的了解

前端构建工具详解


常见考点

前端构建工具是现代前端工程化体系中的核心内容之一,涉及模块打包、依赖管理、性能优化、开发体验提升等多个方向。面试中考察点通常会覆盖原理、使用、性能优化、以及工具演进。

一、基础认知类

考点说明
构建工具的作用为什么需要构建工具?解决哪些问题(如模块化、压缩、兼容性、自动化)?
常见构建工具webpack、Vite、Rollup、esbuild、Parcel 各自特点与适用场景
构建流程概念从源码到产物的完整流程(解析 → 转换 → 打包 → 优化
与脚手架区别构建工具负责打包,脚手架(如 create-react-app、Umi)是上层封装

二、Webpack 方向(核心考点)

考点说明
核心概念Entry、Output、Loader、Plugin、Module、Chunk、Bundle 区别
Loader处理不同类型的文件,如 babel-loader、css-loader、file-loader
Plugin拓展构建流程的功能,如 HtmlWebpackPlugin、DefinePlugin、MiniCssExtractPlugin
HMR(热更新)原理、devServer 实现机制
Tree Shaking原理(ES Module 静态分析)、副作用文件配置(sideEffects)
Code Splitting动态导入与分包策略
缓存优化hash、chunkhash、contenthash 区别与应用场景
性能优化多进程构建(thread-loader)、持久化缓存(cache)、babel 编译优化
webpack 与 Vite 的区别构建速度、原理、依赖处理方式

三、Vite 方向(现代化考点)

考点说明
Vite 的核心原理基于原生 ES 模块(ESM)的按需加载机制
构建阶段区别开发时使用 esbuild 启动快;生产时用 Rollup 打包
热更新机制(HMR)基于 ESM 的模块热替换,无需重新构建全量 bundle
插件系统Vite 插件与 Rollup 插件兼容性
环境变量.env 文件加载、import.meta.env 使用
常见优化方向依赖预构建(Pre-Bundling)、SSR 支持、分包策略

四、Rollup 方向(库开发常考)

考点说明
核心特性面向库的打包,支持 ES、CJS、UMD 等格式
与 Webpack 区别专注于打包 JS 模块,输出简洁、无多余运行时代码
Tree Shaking更加彻底的静态分析能力
插件系统rollup-plugin-babel、rollup-plugin-node-resolve 等常用插件
使用场景通常用于 npm 库、UI 组件库的打包方案

五、esbuild、swc(高性能方向)

考点说明
编译速度快的原因基于 Go(esbuild)或 Rust(swc)编写,利用多线程与增量编译
与 babel、webpack 的关系可作为 babel-loader 替代品或底层构建加速器
在 Vite、Next.js 中的应用被广泛用作底层依赖预构建或转译工具

六、构建性能优化方向(高级考点)

优化点说明
构建体积优化Tree Shaking、Scope Hoisting、代码分割、压缩(Terser、ESBuild Minify)
构建速度优化缓存(babel-loader cacheDirectory)、多进程、预编译依赖
资源优化图片压缩、字体拆分、懒加载、按需加载
开发体验优化HMR、SourceMap、错误提示优化
CI/CD 构建加速缓存 node_modules、分布式构建、增量编译

七、工程化与生态集成考点

考点说明
与框架结合React、Vue、Svelte 各自生态的构建特点(CRA、Vite、Nuxt)
环境变量与配置分离区分 dev、test、prod 配置的方案
自动化构建使用 npm scripts、gulp、vite-plugin、webpack-cli 实现构建自动化
前端监控sourcemap 上传(Sentry 集成)
多页面应用(MPA)构建方案entry 配置、HTML 动态生成、资源路径管理

前端组件化开发


常见考点

一、组件化的认知与本质

考察重点:

  • 是否理解组件化的核心目标:独立、复用、可维护、可组合
  • 能否解释“组件”与“模块”的区别(UI + 状态 + 逻辑 vs 单纯逻辑或数据单元)
  • 是否了解组件化发展脉络: 模板拼接时代 → Vue/React 的声明式组件 → 现代函数式组件设计

典型提问:

你如何理解前端组件化?

前端组件化是将页面拆分为独立、可复用的 “功能单元(组件)” ,每个组件封装自身的结构(HTML)、样式(CSS)、行为(JS)及状态逻辑,通过组件的组合 / 嵌套实现完整页面开发的范式

组件化开发解决了哪些传统前端开发痛点?

传统开发痛点组件化解决方案
代码复用难组件可跨页面复用(如通用按钮 / 弹窗),避免重复编写相同 UI / 逻辑
样式污染组件封装独立样式(如 CSS Modules/Shadow DOM)、独立作用域,避免全局样式 / 变量冲突
维护成本高需求变更仅需修改对应组件,无需改动整个页面代码,定位问题更精准
协作效率低按组件拆分开发任务(如 A 开发表单、B 开发列表),避免代码冲突,并行开发

模块化(如 ES Module)与组件化有什么区别?

维度模块化(ES Module)组件化
核心目标解决 JS 逻辑的拆分 / 复用解决 UI 单元的拆分 / 复用
封装范围仅 JS 逻辑(工具函数、数据处理)结构 + 样式 + 逻辑 + 状态全维度
交互方式import/export 共享逻辑Props / 事件 / 插槽传递数据 / 交互

二、组件设计原则

考察重点:

  • 单一职责原则(SRP) :一个组件只负责一件事
  • 高内聚低耦合:组件内部逻辑完整,外部依赖最小化
  • 可复用性与可组合性
  • 通用组件 vs 业务组件 vs 页面组件 的分层设计

典型提问:

设计组件时,你如何判断逻辑是否应该拆分?

  • 逻辑功能独立:若一段逻辑(如表单校验、数据格式化、状态管理)可脱离当前组件独立存在,且仅服务于某个特定功能(而非组件整体),就该拆分(比如抽成 hooks、子组件、工具函数);
  • 代码体积阈值:单组件逻辑代码超过 200 行(或视觉上明显分块),且不同块无强关联,拆分;
  • 复用需求:某段逻辑在多个地方被使用,必须拆分(避免复制粘贴);
  • 复杂度:若逻辑包含多分支、多状态依赖,拆分后能降低单模块理解成本,就拆分。

如何避免组件间过度耦合?

  • 数据通信规范化:仅通过 props/emit(Vue)、props / 回调(React)传值,不直接操作子组件实例、不共享全局变量(非必要不用 Vuex/Pinia/Redux);

  • 抽象通用逻辑:把耦合的业务逻辑抽成独立 hooks / 工具类,组件只做 UI 渲染 + 调用通用逻辑;

  • 避免硬编码依赖:组件配置通过 props 传入(如按钮文案、跳转链接),不写死;

你在项目中如何区分通用组件与业务组件?

  • 通用组件
  • ① 无业务语义(如按钮、输入框、弹窗、表格);
  • ② 高复用性,可跨项目使用;
  • 仅依赖基础 UI / 交互逻辑不绑定具体业务数据 / 规则
  • ④ 通常放在components/common/目录。
  • 业务组件
  • ① 强业务语义(如订单卡片、商品列表、支付弹窗);
  • 仅服务于特定业务场景,复用范围仅限当前项目;
  • ③ 依赖业务数据模型、接口、业务规则(如订单状态判断);
  • ④ 通常放在components/business/[业务模块]/目录(如components/business/order/)。

四、组件状态与数据流

考察重点:

  • 受控组件与非受控组件(React)
  • 单向数据流思想
  • 局部状态 vs 全局状态的划分
  • 状态同步、缓存、懒加载、数据提升

典型提问:

  • React 组件中,受控与非受控组件的区别是什么?

组件状态应该存在哪里?

  • 仅当前组件使用:存在组件自身(Vue data/ref/reactive,React 的useState);

  • 跨层级 / 多组件共享:用全局状态(Vue 的 Pinia/Vuex,React 的 Context+useReducer/Redux);

  • 非响应式 / 临时状态:直接存在组件变量(如let flag = false),无需响应式系统。

    状态存放原则:局部状态放组件内,共享状态放共同父组件,全局状态放 Context / 状态库,跨生命周期状态放外部存储 localstorage

如何在多个组件间共享状态而不引起频繁重渲染?

  1. 复杂对象(如 userInfo)用 shallowRef
  2. 计算派生值(如 isAdult)用 computed:缓存结果,仅依赖值变了才重新计算和渲
  3. 子组件接收 props 时,只取需要的字段,且关闭复杂对象的深度监听
  4. 拆分细粒度 store,只让组件监听「自己真正需要的状态」,不监听无关数据、不做无效计算,就能避免频繁重渲染

五、组件复用与抽象

考察重点:

  • 高阶组件(HOC)
  • Render Props
  • 自定义 Hooks / Composition API
  • Slot / children / 动态插槽
  • 通用组件库设计(Button、Form、Table)
  • 是否具备抽象复杂逻辑为可复用单元的能力

典型提问:

你在项目中是否写过自定义 Hook?

以 Vue 3 的 Composition API 为核心,自定义 Hook(也叫组合式函数)  本质是把复用的逻辑抽成独立函数,简单易懂的自定义 Hook 写法和思路如下:

一、核心原则
  1. 命名:以 use 开头(如 useRequestuseScroll),符合约定;
  2. 功能:单一职责(一个 Hook 只做一件事),方便复用和维护;
  3. 返回:按需返回响应式数据、方法(让组件只拿需要的内容)。
二、极简示例(一步步写)

高频自定义 Hook:useRequest(Vue3)useRequest 是 Vue 项目中最常用的自定义 Hook,统一封装接口请求的通用逻辑,避免重复代码

核心实现(精简版)

// src/hooks/useRequest.js
import { ref, onUnmounted } from 'vue'

export function useRequest(requestFn, immediate = true) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  let abortController = null

  // 核心请求方法
  const fetchData = async (...args) => {
    loading.value = true
    try {
      abortController = new AbortController()
      const res = await requestFn(...args, { signal: abortController.signal })
      data.value = res.data
      error.value = null
    } catch (err) {
      error.value = err
      data.value = null
    } finally {
      loading.value = false
    }
  }

  // 立即执行/清理副作用
  immediate && fetchData()
  const cancel = () => abortController?.abort()
  onUnmounted(() => cancel())

  return { data, loading, error, fetchData, cancel }
}

组件使用示例

<template>
  <div v-if="loading">加载中</div>
  <div v-else-if="error">请求失败</div>
  <div v-else>{{ list }}</div>
  <button @click="fetchData(2)">刷新</button>
</template>

<script setup>
import { useRequest } from '@/hooks/useRequest'
import axios from 'axios'

// 定义请求函数
const getList = (page = 1, { signal }) => 
  axios.get('/api/list', { params: { page }, signal })

// 使用Hook
const { data: list, loading, error, fetchData } = useRequest(getList)
</script>

核心优势

  1. 复用:统一处理加载、错误、取消请求逻辑;
  2. 灵活:支持手动 / 自动请求、传参、取消请求;
  3. 安全:组件卸载自动清理,避免内存泄漏。

六、组件性能与渲染优化

考察重点:

  • React:memo、useMemo、useCallback、虚拟化渲染、懒加载
  • Vue:v-once、keep-alive、计算属性缓存、异步组件
  • Diff 算法与重渲染机制理解
  • key 的作用与渲染性能影响
  • 大组件拆分、异步加载与按需渲染策略

典型提问:

  • React 组件频繁重渲染的原因有哪些?
  • 你如何设计高性能的长列表组件?
  • Vue 的虚拟 DOM 如何帮助组件更新性能?

七、组件工程化与生态建设

考察重点:

  • 组件库搭建与打包(rollup、vite、storybook)
  • 按需引入与 Tree-shaking
  • 样式隔离与命名规范(CSS Module、Scoped、BEM、Tailwind)
  • 组件测试(Jest、Testing Library、Cypress)
  • 版本管理与发布(npm publish、Monorepo 管理)

典型提问:

  • 你有没有参与过组件库开发?
  • 组件样式冲突如何处理?
  • 如何让组件支持按需加载与主题定制?

八、可维护性与可扩展性

考察重点:

  • 是否关注组件的 接口设计(props)清晰度
  • 是否考虑 可测试性、可扩展性
  • 是否遵循一致的命名规范与目录结构
  • 是否关注团队协作下的组件复用体系(组件资产化)

典型提问:

如何设计一个长期可维护的组件?

封装组件时遵循单一职责、高内聚低耦合、参数可配置性、样式隔离、良好的错误处理和性能优化等原则,确保组件可复用、易维护、性能高并具备清晰的文档

组件 Props 过多时该如何优化?

  1. 合并关联 props 为对象,利用 v-bind 批量传递
  2. 多层嵌套组件传大量 props(props 透传)改用 provide/inject
  3. 若 props 是共享状态(如全局配置、用户信息),不用通过 props 传递,直接用 Pinia/Vuex 管理,组件按需获取即可。
  4. 功能复杂就拆组件,把组件拆成更小的子组件,每个子组件只接收自己需要的 props,不接收无关属性。

如何让一个组件支持多主题或动态配置?

关联面试题

在项目中封装组件时,你会有些什么样的原则?

说说你对 Vue 中异步组件的理解

如果你有一个业务组件库,希望打包输出为 esm + cjs + dts,有什么思路?

如何封装一个 AI 图片生成组件,支持选择模型、输入 prompt、展示图像并支持本地保存?

单页面架构


常见考点

一、SPA 的核心概念与原理

考察重点:

  • SPA 与 MPA 的区别(单页应用 vs 多页应用)

  • SPA 的本质:前端接管路由 + 局部刷新渲染 + 状态持久

  • 单页架构的优缺点:

    • 优点:用户体验流畅、前后端分离、开发效率高
    • 缺点:首屏加载慢、SEO 不友好、复杂状态管理

典型问题:

  • 请解释一下什么是单页面应用?它和多页面应用有何区别?
  • 为什么现代前端框架普遍采用 SPA 架构?
  • SPA 的缺点有哪些?你在项目中如何应对?

二、前端路由机制(核心考点)

考察重点:

  • 前端路由的两种实现方式:

    1. Hash 模式(依赖 window.onhashchange
    2. History 模式(依赖 HTML5 History API,如 pushState/replaceState
  • 路由与组件渲染的映射机制

  • 浏览器刷新时的 404 问题与 Nginx 配置重定向

  • 动态路由与懒加载机制

  • 嵌套路由、路由守卫、导航守卫(beforeEach、onEnter)

典型问题:

  • 你能解释一下前端路由是如何实现的吗?
  • 在 React 或 Vue 中如何实现路由懒加载?
  • 路由跳转时如何做权限校验?

- 为什么 History 模式在刷新时会 404?如何解决?


一、为什么 History 模式刷新会 404?

Vue/Router 的 History 模式依赖浏览器 history.pushState API 实现前端路由,URL 看起来像真实后端路径(如 /user/123),但实际是前端模拟的路由

  • 首次访问:浏览器请求域名根路径(如 www.xxx.com),后端返回 index.html,前端路由接管,匹配 /user/123 渲染对应组件;
  • 刷新页面:浏览器会直接向服务器请求 www.xxx.com/user/123,但后端没有 /user/123 这个物理路径 / 接口,因此返回 404。

(Hash 模式不会有这个问题,因为 # 后的内容不会传给服务器,刷新只会请求根路径

二、解决方法(核心:让后端兜底返回 index.html)

1. 开发环境(Vue CLI/Vite)
  • Vue CLI:在 vue.config.js 配置 devServer 重定向:

    module.exports = {
      devServer: {
        historyApiFallback: true // 所有404请求都重定向到index.html
      }
    }
    
2. 生产环境(后端配置)

核心逻辑:所有非接口请求都返回前端的 index.html,由前端路由匹配

  • Nginx 配置:

    location / {
      try_files $uri $uri/ /index.html; # 先找物理文件,找不到就返回index.html
    }
    
  • Apache 配置:修改 .htaccess

    FallbackResource /index.html
    
  • Node.js(Express):

    const express = require('express')
    const app = express()
    app.use(express.static('dist')) // 前端打包目录
    // 所有请求返回index.html
    app.get('*', (req, res) => {
      res.sendFile(__dirname + '/dist/index.html')
    })
    

核心总结

404 根源是「前端路由路径被浏览器当作后端请求」,解决关键是让后端把所有前端路由路径的请求,都兜底返回 index.html,交由前端路由处理。

三、首屏加载与性能优化

考察重点:

  • SPA 首屏性能瓶颈:JS 体积大、阻塞渲染、数据依赖多

  • 优化手段:

    • 代码分割、懒加载(dynamic import
    • 资源预加载(<link rel="preload">
    • 骨架屏 / loading 占位
    • SSR / SSG / Prerender
    • 缓存策略(localStorage、indexedDB、Service Worker)
  • 打包优化:Tree-shaking、动态 Polyfill、CDN 分发

典型问题:

  • 为什么 SPA 首屏加载慢?如何优化?
  • 你在项目中如何衡量并优化首屏渲染时间?
  • 是否用过骨架屏?如何实现?
  • SSR 与 CSR 的区别?哪种更适合你的业务?

四、状态管理与页面间数据共享

考察重点:

  • 全局状态管理工具的选择与设计(Redux、Zustand、MobX、Pinia、Vuex)
  • 单页多模块间数据同步
  • 状态持久化(localStorage + redux-persist)
  • 页面切换时的状态保留策略(keep-alive、缓存路由组件)

典型问题:

  • SPA 中多个页面之间如何共享数据?
  • 你如何在 React/Vue 中实现页面状态缓存?
  • Redux / Vuex 为什么适合单页应用?

五、SEO 与可访问性问题

考察重点:

  • SPA 对 SEO 的影响(内容渲染在客户端)

  • 解决方案:

    • SSR(服务端渲染)
    • 静态化预渲染(Prerender)
    • 动态渲染(Dynamic Rendering)
  • 是否理解 SSR 的原理与前后端同构机制

典型问题:

  • 为什么单页面应用不利于 SEO?
  • 你知道哪些改善 SEO 的方案?
  • SSR、SSG、Prerender 各有什么适用场景?

六、页面切换与体验优化

考察重点:

  • 路由切换动画实现方式(Transition / Framer Motion)
  • 滚动位置还原、懒加载与预加载
  • SPA 下的浏览器前进/后退行为管理
  • 缓存页面状态与回退体验(如 keep-alive、React Router useNavigate(-1)

典型问题:

  • 单页应用如何实现页面切换动画?
  • 如何在页面回退时保持滚动位置?
  • React Router 中如何实现页面缓存?

七、工程架构与部署策略

考察重点:

  • 前后端分离架构下的 SPA 部署流程
  • 静态资源路径管理、Nginx 配置、路由重写
  • 灰度发布、版本缓存控制
  • 与后端接口的协作(API Gateway / BFF)

典型问题:

  • SPA 应用如何配置 Nginx?
  • 当路径直接访问子页面时如何避免 404?
  • 如何在单页架构中支持国际化、多主题?

八、安全与容错

考察重点:

  • 路由权限控制(前端路由守卫 + token 校验)
  • 登录态维护与刷新机制(cookie / token / refresh token)
  • 错误边界处理与白屏监控
  • 版本更新提示与缓存失效策略(Service Worker + manifest 版本对比)

典型问题:

  • SPA 如何实现路由级别的权限校验?
  • 用户 token 过期时如何优雅地处理?
  • 如何避免单页应用白屏问题?

九、进阶与扩展方向

考察重点:

  • 微前端(Micro-Frontend) :将多个 SPA 组合成一个系统
  • 多实例 SPA 与路由隔离
  • 同构渲染与前后端协作模式(Next.js、Nuxt.js)

典型问题:

  • 你对微前端架构的理解?
  • 微前端与传统 SPA 的关系是什么?
  • SSR 是如何解决 SPA 的首屏性能和 SEO 问题的?

关联面试题

SPA首屏加载速度慢的怎么解决

说说你对SPA的理解

SPA(单页应用)首屏加载速度慢怎么解决?

为什么 SPA 应用都会提供一个 hash 路由,好处是什么?

SPA应用怎么进行SEO?

前端模块化


babel概念及原理 🌟

babel 可以将代码转译为想要的目标代码,并且对目标环境不支持的api 自动 polyfill。而babel实现这些功能的流程是 解析(parse)-转换(transfrom)-生成(generator),接下来我们就看看每个流程都做了啥工作

  • 解析:根据代码生成对应的AST结构

  • 转换:遍历AST节点并生成新的AST节点:(将 AST 中的 ES6 特性转换为 ES5 兼容的代码)。

  • 生成:根据新的AST生成目标代(生成对应的 ES5 代码字符串

image.png

为什么pnpm比npm快

pnpm 用硬链+内容寻址,磁盘省 50%、装包快 2-3 倍

pnpm 比 npm 快的核心原因在于它的 存储机制和安装策略 更高效,主要体现在以下几点:

  1. 硬链接 + 符号链接(软链接)的存储方式pnpm 会将所有安装过的包统一存储在一个全局共享的 "store" 目录中,不同项目安装同一版本的包时,不会重复下载,而是通过 硬链接 直接复用全局存储的文件,仅用 符号链接 指向项目的 node_modules 目录。而 npm 会为每个项目单独复制一份依赖文件,重复安装相同包时会浪费时间和磁盘空间。
  2. 避免冗余依赖安装pnpm 严格遵循 "扁平化依赖树" 的逻辑,通过符号链接构建依赖关系,不会像 npm(尤其是旧版本)那样在 node_modules 中嵌套复制大量重复依赖,减少了文件复制和 I/O 操作的开销。
  3. 高效的依赖解析pnpm 在解析依赖时更直接,避免了 npm 中一些冗余的处理步骤(如旧版本的依赖树扁平化算法),进一步提升了速度。

简单说:pnpm 靠 "共享存储 + 链接复用" 减少了重复下载和文件复制,自然比 npm 快。

npm run start 的整个过程 🌟

以下是 npm run start 的简明执行过程:

1. 解析命令

  • 查找脚本:在 package.jsonscripts 字段中查找 start 命令。
    {
      "scripts": {
        "start": "react-scripts start"
      }
    }
    

2. 执行脚本

  • 调用命令:运行 start 对应的命令(如 react-scripts start)。
  • 环境变量注入:自动注入 NODE_ENV=development(开发环境)。

3. 启动开发服务器

  • 初始化构建工具:调用 Webpack/Vite 等工具,加载配置文件。
  • 编译代码:将源代码打包为浏览器可执行的文件(如 JS、CSS)。
  • 启动本地服务器:通过 Express 或 Webpack Dev Server 托管静态资源

4. 热更新(HMR)

  • 监听文件变化:实时监控文件修改。
  • 局部更新:通过 WebSocket 推送更新内容,浏览器局部刷新。

5. 打开浏览器

  • 自动访问:默认打开 http://localhost:3000(端口可配置)。

npm install的执行过程 🌟

执行过程:

  1. 解析依赖
    读取 package.json 中的 dependenciesdevDependencies,确定需安装的包及版本范围。
  2. 检查锁定文件
    若存在 package-lock.json,则按其中记录的精确版本安装;否则根据 package.json 的版本范围解析依赖树。
  3. 下载与缓存
    优先从本地缓存(~/.npm)获取依赖包;若未命中则从 npm 仓库(或镜像)下载,并存入缓存。
  4. 扁平化安装
    将依赖包解压至 node_modules,通过依赖提升(Dedupe)减少嵌套:相同包且版本兼容时提升至顶层,冲突版本则嵌套安装。
  5. 生成锁文件
    创建或更新 package-lock.json,锁定所有依赖的确切版本及结构,确保环境一致性。

常见问题与优化

问题原因与解决
安装速度慢使用国内镜像(npm config set registry https://registry.npmmirror.com)或 pnpm
node_modules 体积过大使用 npm dedupe 减少冗余,或切换到 pnpm(硬链接节省空间)
版本冲突(Peer Deps)根据警告手动调整版本,或使用 npm install --force 强制覆盖
锁文件冲突禁止手动修改锁文件,始终通过 npm install 自动更新

总结

npm install 的核心流程是:解析依赖 → 检查锁定文件 → 下载包 → 扁平化安装 → 生成锁文件

eslint概念以及原理 🌟

ESLint 概念

ESLint 是一个用于 JavaScript/TypeScript 的静态代码分析工具,用于检测代码中代码质量检查(如未定义变量) 和风格统一。

核心原理

1. 解析代码为 AST

ESLint 使用 Parser 将源代码转换为抽象语法树(AST)

2. 遍历 AST 并应用规则

ESLint 使用访问者模式(Visitor Pattern)遍历 AST 节点,对每个节点应用配置的规则:

    // 规则示例:禁止使用 var
    module.exports = {
      create(context) {
        return {
          // 当遇到 VariableDeclaration 节点时触发
          VariableDeclaration(node) {
            if (node.kind === 'var') {
              context.report({
                node,
                message: 'Unexpected var, use let or const instead.'
              })
            }
          }
        }
      }
    }
3. 生成并输出报告

遍历完成后,ESLint 收集所有违规信息,生成报告并输出:

    /path/to/file.js
      1:1  error  Unexpected var, use let or const instead.  no-var
      5:3  warning  Expected indentation of 2 spaces but found 4  indent

    * 2 problems (1 error, 1 warning)

作用场景

  • 代码质量检查(如未定义变量)
  • 风格统一(如引号、缩进)
  • 团队协作规范强制执行。

package.json文件中的devDependdencies和dependdencies对象有什么区别

字段核心含义安装 / 打包行为适用场景
dependencies项目运行时必需的依赖(生产环境也需要)npm install 会安装;打包时(如 webpack)会被包含到产物中业务功能核心依赖(如 React/Vue、axios、lodash、路由库、状态管理库等)
devDependencies开发 / 构建阶段需要的依赖(生产环境不需要)npm install 会安装;npm install --production 或生产打包时会忽略构建 / 调试工具(如 webpack、babel、eslint、jest、rollup、测试库等)
peerDependencies声明项目期望宿主环境提供的依赖(避免重复安装,保证版本兼容)不会自动安装,仅提示用户需手动安装指定版本;宿主需满足版本要求插件 / 库开发(如 Vue 插件需宿主提供 Vue、ESLint 插件需宿主提供 ESLint)

什么是CI/CD

  • CI(持续集成,Continuous Integration)
    开发者频繁将代码合并到主分支,自动触发构建和测试,确保代码质量。
  • CD(持续交付/部署,Continuous Delivery/Deployent)
    将通过测试的代码自动部署测试或生产环境,快速交付新功能。

常见考点

  1. 触发:push / PR → GitHub Actions / GitLab CI / Jenkins 自动拉起流水线。

  2. 流程:安装依赖 → lint → test → build → 上传产物 → 部署。

  3. 产物:dist 包打 zip / Docker 镜像,版本号由 changesets 自动生成。

  4. 部署:

    • 静态:CI 里 rsync / aws s3 sync 到 CDN;
    • 容器:docker build → 推送仓库 → K8s 滚动更新。
  5. 回滚:保留最近 N 个版本,一键 revert;监控告警异常自动触发回滚脚本。

Mainifest 文件是什么,有什么用

Mainfest(更新清单),通常是一个 JSON 文件。需要配置 WebpackManifestPlugin 插件 在 Webpack 输出阶段生成,用于记录所有模块及其依赖关系的映射用来管理模块加载、优化浏览器缓存。 包含:

  • 模块标识符: 每个模块都有一个唯一标识符,这些标识符用于在运行时查找和加载模块。
  • Chunk 映射关系:包含 chunk 与包含的模块之间的映射关系,以及 chunk 之间的依赖关系。这有助于运行时确定哪些 chunk 需要被加载。
  • Hash 值: 每个输出文件的 hash 值。有助于浏览器判断文件是否有更新,从而决定是加载缓存中的资源还是重新请求新的资源。
    {
      "main.js": "main.1a2b3c4d5e6f7g8h9i0j.js",
      "vendor.js": "vendor.1a2b3c4d5e6f7g8h9i0j.js"
    }

生成的 Manifest 文件可以用于以下场景:

  • 服务端渲染: 在服务端渲染时,可以使用 Manifest 文件来生成正确的脚本标签,确保引用最新的资源。
  • 缓存管理: 通过记录文件的哈希值,确保在文件内容变化时,客户端能够获取到最新的文件,而不是使用缓存的旧文件。
  • 动态加载: 在需要按需加载模块时,可以使用 Manifest 文件来查找模块的路径。