Skip to content

工程化、工具与跨端 ​

1. 小程序可以做哪些性能优化? ​

答案:

小程序优化重点是启动、渲染和网络:按页面拆包、压缩代码并延迟非首屏逻辑;减少 setData 数据量和调用次数,避免频繁更新长列表;图片按需加载、压缩并使用合适尺寸;接口合并、缓存和预取,长列表使用虚拟化或分页。还要用开发者工具性能面板验证,兼顾不同机型,不能只追求包体积而忽略首屏体验。

2. 你了解 Git 的 Worktree 吗? ​

答案:

Git Worktree 允许一个仓库同时检出多个工作目录,每个目录绑定不同分支,共享同一套对象库,因此比重复 clone 更省空间。常用 git worktree add ../feature feature 创建、git worktree list 查看、git worktree remove ../feature 删除。它适合并行开发、临时修复和隔离构建;同一分支不能同时被多个 worktree 检出,删除目录后应执行 prune 清理记录。

3. webpack 中的 Loader ,链式调用顺序会影响编译结果吗? ​

答案:

会影响。Webpack 对同一规则命中的 loader 按从右到左、从后往前执行,前一个 loader 的输出会成为后一个 loader 的输入。例如先由 ts-loader 把 TypeScript 转成 JavaScript,再由 Babel 做兼容转换;顺序反过来可能导致 Babel 无法解析或处理类型语法。enforce: 'pre'/'post' 还会改变阶段,loader 必须明确输入输出类型和 source map 传递。

4. 跨平台的开发中,如何处理不同平台的 API 差异? ​

答案:

先定义平台无关的业务接口,再用适配器隔离平台 API:统一参数、返回值、错误码和能力检测,Web、iOS、Android、小程序分别提供实现。优先使用跨端框架的公共 API,差异较大时通过条件编译或平台目录分支;不要在业务层散落平台判断。对权限、文件、支付等能力提供降级方案,并用各平台集成测试验证真实行为。

5. 如何实现Webpack的按需加载与预加载? ​

答案:

按需加载依赖动态 import(),Webpack 会把模块拆成异步 chunk,例如 import('./editor'),在路由进入或用户触发时请求。预加载可写成 import(/* webpackPrefetch: true */ './editor'),主资源完成后浏览器低优先级获取;webpackPreload 则更积极,适合当前很快需要的资源。应结合网络、缓存和首屏指标使用,避免预取过多造成带宽竞争。

6. 小程序中 setData 的性能问题如何优化? ​

答案:

setData 会把数据从逻辑层传到视图层,频繁调用或传递大对象会增加序列化、通信和渲染成本。应只更新变化字段,使用路径写法如 items[3].name,合并同一事件中的多次更新;不要把整个列表、页面数据或临时对象传入。长列表采用分页、虚拟列表和局部组件,事件回调中避免无意义 setData,并用性能面板定位热点。

7. 跨端应用常见的性能瓶颈有哪些?优化方案是什么? ​

答案:

常见瓶颈包括启动包过大、JS 执行和桥接通信频繁、跨端组件渲染开销、长列表布局、图片与网络请求,以及不同平台能力不一致造成的重复处理。优化应先测量:拆包和懒加载,减少跨层数据传输,列表虚拟化,图片压缩与缓存,接口合并和预取;对高频动画尽量交给原生或 GPU,并按平台分别验证帧率、内存和首屏时间。

8. 谈谈 React 与 React Native 的区别 ​

答案:

React 是用于构建 Web 用户界面的 JavaScript 库,最终渲染 DOM,依赖浏览器平台;React Native 复用 React 的组件、状态和协调机制,但把组件渲染为 iOS/Android 原生控件,并通过 JS 与原生模块通信。两者不能完全共用 DOM/CSS,RN 有独立布局和原生能力;共享业务逻辑较容易,共享视图通常要抽象平台组件。

9. Taro 如何实现 React 到小程序代码的转换? ​

答案:

Taro 先把 React/JSX 代码交给 Babel 解析成 AST,再通过编译插件识别组件、事件、生命周期和样式,转换为目标小程序的 WXML、WXSS、JS 及配置文件;运行时负责状态更新、事件映射和平台能力封装。它并非把 DOM 原样搬过去,动态特性和第三方 Web 组件会受限制,需要遵循 Taro 支持的组件语法,并通过条件编译处理平台差异。

10. 解释 uni-app 的编译原理和渲染机制。 ​

答案:

uni-app 以 Vue/组合式代码为输入,编译器解析模板、脚本和样式,再依据目标平台转换组件、指令、生命周期、CSS 与配置;小程序端生成对应模板和逻辑文件,App 端通常通过运行时和原生渲染层执行,Web 端则输出 HTML/CSS/JavaScript。状态变化由响应式系统触发更新,平台 API 由条件编译和统一封装适配。不同端不是完全同构,需检查组件与 API 兼容性。

11. 混合开发(Hybrid)的原理是什么? ​

答案:

Hybrid 把原生壳与 Web 内容结合:原生负责窗口、导航、权限和系统能力,WebView 加载 H5 页面,JavaScript 与原生通过桥接协议双向通信。页面可离线缓存并由原生注入版本和能力,调用支付、相机等能力时走桥接;原生也能向页面发送事件。优势是迭代快、复用 Web 代码,代价是 WebView 性能、桥接开销和平台兼容性,关键链路通常需要原生实现或降级。

12. 如何检测模块之间的循环依赖? ​

答案:

可在构建阶段用依赖图工具检测,例如 madge --circular src 或 dependency-cruiser,也可让 ESLint 的 import/no-cycle 在开发和 CI 中提示。原理是把模块作为节点、导入关系作为有向边,用 DFS 或强连通分量算法找回到自身的路径。发现循环后应定位共同类型、抽取低层模块或改为依赖注入;要区分真实运行时循环与仅类型导入,避免误报。

13. 从 yarn 迁移到 pnpm 的过程中,需要注意哪些问题?如何避免幽灵依赖? ​

答案:

迁移前锁定 Node、包管理器和依赖版本,提交 pnpm-lock.yaml,核对 workspace、脚本、私有源、原生依赖和 CI 缓存;先运行测试与生产构建。pnpm 默认严格的非扁平结构会暴露未声明依赖,修复方式是把代码实际 import 的包写入对应 package.json,而不是依赖提升;必要时用 public-hoist-pattern 作为过渡。迁移后用 pnpm install --frozen-lockfile 验证可复现性。

14. 如何优化打包后的 JS bundle 体积? ​

答案:

先用 bundle analyzer 找出大模块、重复依赖和重复 polyfill,再做路由级动态导入、按需引入组件、替换重型库、开启 Tree Shaking 和压缩,抽离稳定的 vendor 并利用缓存。检查 source map、测试代码和调试依赖是否误进生产包,避免整包引入工具库。优化要同时看 gzip/brotli 后体积、首屏请求数和运行时成本,不能只按原始 bundle 大小判断。

15. Hippy、React Native、Taro 和 uni-app 的区别是什么?它们各自的优势在哪里? ​

答案:

React Native 和 Hippy 都偏原生渲染:以 JS 驱动原生组件,适合追求接近原生的移动端性能;RN 生态成熟、社区广,Hippy 更强调高性能和与既有原生容器集成。Taro 主要把 React/Vue 风格代码编译到小程序、H5 等多端,开发效率高但受目标端语法约束;uni-app 以 Vue 生态为主,覆盖小程序、App、Web 广。选型取决于目标平台、原生能力、团队栈和性能要求。

16. 如何编写 Babel 插件?Babel 的工作流程是怎样的?不同预设的作用是什么? ​

答案:

Babel 流程是解析源码得到 AST,遍历 AST 执行插件访问器,最后生成代码和 source map。插件通常返回 visitor,在节点访问时读取或替换 AST,例如修改 CallExpression;要注意作用域、路径 API 和避免重复遍历。preset 是一组插件的预配置集合:如 @babel/preset-env 根据目标浏览器转换语法,preset-react 处理 JSX,preset 不等于运行时 polyfill,必要时还需配置 core-js 等。

17. 你了解 rspack 吗?它与 Webpack 的区别是什么? ​

答案:

Rspack 是用 Rust 编写、兼容 Webpack 配置和生态的打包器,核心目标是更快的解析、模块图构建和编译。Webpack 以 JavaScript 实现,loader/plugin 生态更成熟、兼容性经验更广;Rspack 可复用大量规则,但少数 loader、插件或边界行为仍需验证。迁移时先检查自定义插件、loader、Module Federation、source map 和产物差异,再用构建耗时、体积与运行结果做基准,而不是只看宣传速度。

18. 如何治理项目中的 barrels files? ​

答案:

Barrel file 是集中导出模块的入口,如 index.ts。治理重点是控制边界:公共包可保留稳定入口,内部模块尽量直接导入,避免跨层级 barrel;使用显式导出代替 export *,防止命名冲突和无意暴露。分析器发现循环依赖后拆分入口,关注 Tree Shaking 是否因副作用或聚合导入失效,并在 lint、包边界和构建分析中持续约束。

19. 在避免幽灵依赖上,有哪些实践? ​

答案:

幽灵依赖是代码使用了未在自身 package.json 声明、却因扁平化安装恰好可解析的包。实践上使用 pnpm 等严格解析的包管理器,所有直接 import 都显式声明,提交并冻结 lockfile;在 CI 执行干净安装、依赖审计和构建测试,启用 ESLint import 规则或 dependency-cruiser 检查。发布库时把宿主需提供的依赖放进 peerDependencies,不要靠手工 hoist 掩盖问题。

20. Vite 开发服务器和浏览器之间是如何通信的(websocket + HTTP/1.1)?引入 HTTP/2 或 HTTP/3 会带来哪些改进? ​

答案:

Vite 开发时浏览器用 HTTP/1.1 请求模块、源码和静态资源,服务器按需转换后返回;同时通过 WebSocket 建立 HMR 通道,文件变化后服务器推送更新消息,浏览器再请求新模块。HTTP/2 的多路复用、头部压缩和连接复用可减少并发请求阻塞,但开发服务器的收益取决于 TLS、代理和浏览器支持;HTTP/3 基于 QUIC,弱网下建连和丢包恢复更好,却增加部署复杂度,不能替代 HMR 通道。

21. Vite 热更新(HMR)的原理是什么?从文件增量更新开始,整个过程是如何实现的? ​

答案:

文件变更由 Vite 监听器发现后,先更新模块图并使受影响模块缓存失效,再沿 import 关系向上查找能处理 HMR 的边界;服务器通过 WebSocket 推送更新类型、路径和时间戳。客户端收到后动态导入新模块,执行模块的 accept 回调并更新状态;若没有可接受边界或依赖链失效,就执行整页刷新。CSS 通常可直接替换,模块 dispose 可清理旧副作用。

22. Vite 预构建的原理和作用是什么? ​

答案:

Vite 预构建主要处理 node_modules 中的依赖:扫描入口分析依赖,用 esbuild 将 CommonJS/UMD 转为 ESM,并把深层依赖合并为较少的浏览器请求,结果缓存到 node_modules/.vite。这样既解决浏览器不能直接运行 CommonJS 的问题,也减少开发时请求数量;依赖锁文件或配置变化会触发重新构建。它属于开发阶段优化,不等同于生产构建,动态依赖和副作用仍需正确配置。

23. 如何优化 pnpm 的分包策略? ​

答案:

pnpm 的分包通常指 workspace 的 package 划分与发布边界。按业务域和稳定依赖拆包,公共能力放独立包,避免所有包互相依赖;使用 workspace:* 管理内部版本,统一 lockfile 和 Node 版本。发布包明确入口、exports、files 与 peerDependencies,避免把构建工具带入运行时;配合 changesets 管理版本,并通过依赖图、安装时间、构建缓存和包体积验证拆分是否真的降低耦合。

24. Webpack的Tree Shaking与Vite的Dead Code Elimination有何异同? ​

答案:

两者都依赖静态模块分析,目标是删除未使用的导出或代码,前提是模块副作用可被安全判断。Webpack 在生产构建中基于 ESM 标记进行 Tree Shaking,再由压缩器删除不可达语句;Vite 开发阶段依赖预构建,生产通常交给 Rollup 做模块级 Tree Shaking,并由压缩器做 Dead Code Elimination。动态 require、顶层副作用、CommonJS 和错误的 sideEffects 配置都会限制效果,需结合产物分析验证。

25. Hash模式如何影响SEO?如何通过预渲染优化? ​

答案:

Hash 路由把路径放在 # 后,浏览器不会把 hash 发送给服务器,服务端和搜索引擎通常只能看到同一个入口 URL,页面内容不利于按路由建立索引,也不利于分享和规范化。可改用 history 路由并配置服务端回退;若必须使用 hash,可在构建阶段对重要路由预渲染为静态 HTML,输出 title、description、canonical 和结构化数据,再用客户端接管交互。

26. 小程序的页面加载流程是怎样的?请简要描述从用户点击小程序图标到页面显示的整个过程。 ​

答案:

用户点击图标后,微信先校验并启动小程序进程,加载基础库、主包和运行环境;随后初始化 App,读取路由与页面配置,创建页面实例并执行 onLoad、onShow 等生命周期。页面模板和样式完成解析后,逻辑层计算初始数据,通过框架通信同步到视图层渲染;网络请求、图片解码和字体等资源可能继续异步完成。冷启动通常更慢,热启动会复用进程和部分缓存。

27. 分析微信小程序和微信内的 H5 网页在性能上的区别,以及导致这些区别的原因是什么? ​

答案:

小程序通常有更严格的包体积、分包和逻辑层到视图层通信限制,但页面结构和运行环境可控,原生组件与离线缓存能带来稳定体验;微信 H5 依赖 WebView,首次要下载 HTML、JS、CSS 和资源,受浏览器解析、布局、网络及 WebView 版本影响,但可使用成熟 Web API 和缓存策略。性能差异来自运行时架构、通信方式、资源加载和平台限制,不能简单断言谁一定更快,应以首屏、交互延迟和内存实测。

28. 说说 package.json 中 dependencies、devDependencies、peerDependencies 有哪些不同,分别适合什么场景 ​

答案:

dependencies 是运行项目或产物所需的直接依赖,部署生产环境必须可用;devDependencies 只服务于开发、测试、构建,如 TypeScript、ESLint 和打包器。peerDependencies 表示库要求宿主项目提供兼容版本,常用于 React、插件和组件库,避免库重复安装核心宿主。它们不是绝对按环境决定,关键看谁在运行时 import;发布前应检查打包方式和安装器行为。

29. 小程序如何实现rpx到px的转换?如何适配不同屏幕密度的设备(如Retina屏)? ​

答案:

rpx 以屏幕宽度 750 作为设计基准:在宽度为 W 的设备上,1rpx = W / 750 px,因此设计稿中的 n rpx 换算为 n * W / 750 个逻辑像素。小程序运行时会按设备宽度完成布局,开发者通常直接使用 rpx,不应按物理像素和 DPR 手工放大。图片清晰度另按 devicePixelRatio 准备合适资源;安全区、横屏和极端宽度仍需实机测试。

30. git pull 命令实际上是哪两个操作的融合? ​

答案:

git pull 默认是 git fetch 加 git merge:先从远程获取提交并更新远程跟踪分支,再把对应远程分支合并到当前本地分支。也可以配置 pull.rebase=true,此时第二步变为 rebase,即 fetch + rebase。fetch 不会改当前分支,pull 可能产生合并提交或冲突;需要更可控时先 fetch,再明确选择 merge、rebase 或仅查看差异。

31. git merge master 与 git merge origin master 有什么区别? ​

答案:

git merge master 合并的是本地名为 master 的分支,内容取决于本地是否及时同步;git merge origin master 按 Git 的参数解析通常等价于把 origin 和 master 当作两个待合并参数并报错,正确写法是 git merge origin/master,它合并远程 origin 的远程跟踪分支。若想合并远程最新状态,应先 git fetch origin,再选择 origin/master。

32. 怎么处理微信小程序里的静默授权异步问题? ​

在微信小程序中,静默授权(wx.login)是异步的。假设我们系统是通过微信静默授权,拿到用户的 openid 作为唯一标志。

我们在页面 onLoad 里需要等授权完成,拿到openid才能继续后续接口调用。由于静默授权的异步性,我们怎么能保证,在全局能够知调用 wx.login 一次,而不会每个页面都去重复调用 wx.login ?

答案:

把授权请求提升到 App 或单例服务,并缓存一个 Promise,让并发页面共享同一次流程;成功后保存 openid 或服务端会话,失败则清理 Promise 允许重试。页面只等待 getOpenId(),不直接调用 wx.login。核心实现:

js
let loginPromise
export function getOpenId() {
  if (!loginPromise) loginPromise = new Promise((resolve, reject) => {
    wx.login({ success: ({ code }) => apiLogin(code).then(resolve, reject), fail: reject })
  }).catch(err => { loginPromise = null; throw err })
  return loginPromise
}

这样可合并并发调用;登录态过期时主动清缓存重建。

33. 浏览器是否支持 CommonJs 规范? ​

答案:

浏览器原生不支持 CommonJS。CommonJS 依赖运行时的 require、module.exports 和同步加载,浏览器没有这些 Node.js 模块对象;浏览器通常使用 ES Module 的 import/export。Webpack、Browserify 等构建工具会先分析依赖图,再把模块和一个运行时模块加载器打包进脚本,因此表现为“浏览器能运行 CommonJS”,本质是工具完成了转换与模拟。

34. 说说你对 V8 引擎的了解 ​

答案:

V8 是 Google 用 C++ 编写的 JavaScript 与 WebAssembly 引擎,Chrome 和 Node.js 都使用它。它先解析源码生成 AST,再生成字节码,由解释器执行,并通过运行时反馈识别热点代码,用 JIT 编译为机器码;对象隐藏类、内联缓存和垃圾回收也用于提速。优化代码若发生类型等假设失效会反优化,所以稳定的数据形状和避免频繁类型变化有助于性能。

35. 为什么小程序中无法使用 dom 相关的 api? ​

答案:

小程序页面通常运行在受控的逻辑层与视图层环境中,不直接暴露浏览器 DOM,也不允许任意操作宿主页面。这样可以隔离业务代码与渲染实现,统一不同设备的运行行为,并限制越权访问和高频 DOM 操作带来的性能问题。开发者应使用框架提供的组件、数据绑定、选择器查询和节点测量 API;这些 API 是平台抽象,不等同于原生 DOM。

36. 如何组织 monorepo 工程? ​

答案:

组织 monorepo 时先按 apps 与 packages 分离应用和可复用包,再用 pnpm workspace、npm workspaces 或 Nx/Turborepo 管理依赖、任务和缓存。包应有清晰边界、独立 package.json 与入口,避免跨包引用源码内部路径;统一 TypeScript、Lint、测试和构建配置,锁定依赖版本。CI 按受影响包增量执行,并配合变更集、版本发布和依赖约束,防止仓库变大后失控。

37. ES6 代码转成 ES5 代码的实现思路是什么? ​

答案:

通常由 Babel 完成语法转换,再按目标环境注入必要的 polyfill。解析器把 ES6 源码转成 AST,转换插件将箭头函数、类、解构、模块等改写为 ES5 可表达的语法,生成器再输出代码和 source map;@babel/preset-env 根据 browserslist 选择转换范围。语法转换不能凭空提供缺失 API,因此 core-js 等 polyfill 要按使用情况注入,并注意全局污染与包体积。

38. 什么情况下会导致 webpack treeShaking 失效? ​

答案:

Webpack tree shaking 依赖静态可分析的 ES Module,并通常只在 production 优化中生效。使用 CommonJS、动态且不可分析的导入、通过对象或字符串间接访问导出,或把模块标记为有副作用,都会降低删除精度;sideEffects 配置错误也可能导致保留过多,甚至误删必要初始化。还要确认压缩器已启用,因为 Webpack 标记未使用导出,最终删除通常由 Terser 等完成。

39. babel 的工作流程是怎么样的? ​

答案:

Babel 的流程可概括为解析、转换、生成。解析器读取源码并依据插件配置生成 AST,同时检查语法;转换阶段由 preset 和 plugin 遍历、修改 AST,完成语法降级、模块处理或自定义变换;生成器把新 AST 输出为目标代码,并可生成 source map。配置由 babel.config 或项目配置加载,插件执行顺序会影响结果;Babel 主要转换语法,运行时 API 兼容仍需 polyfill。

40. webpack 是如何给 web 应用注入环境变量的,说说它的原理 ​

答案:

Webpack 通常通过 DefinePlugin 或 EnvironmentPlugin 注入环境变量。构建时读取 Node 进程中的 process.env,把代码中匹配的变量替换成字面量,例如 process.env.NODE_ENV 直接变成字符串;这不是浏览器运行时读取系统环境。字面量替换还能让压缩器删除开发分支。变量会进入前端产物,不能放密钥;应区分开发、测试、生产配置,并明确缺省值和类型。

41. 说说 webpack 异步加载的原理 ​

答案:

遇到 import() 或配置的异步入口时,Webpack 会把依赖图拆成主 chunk 与异步 chunk,并为异步模块生成唯一 id 和运行时加载映射。浏览器执行 import() 时,Webpack runtime 创建 <script> 标签请求对应文件,维护加载状态并返回 Promise;文件成功执行后注册模块,Promise 才完成,失败则 reject。生产环境还会结合 publicPath、hash 和预加载配置定位、缓存这些 chunk。

42. webpack 中的 externals 作用是什么? ​

答案:

externals 用来告诉 Webpack 某些依赖不要打进 bundle,而是在运行时从外部提供。例如把 React 标记为 external 后,代码仍可 import React,但产物会改为读取页面上的全局变量或由 CommonJS、AMD 等宿主提供。它能减少包体积、避免重复打包,并适合由 CDN 或宿主统一管理的库;同时必须保证外部脚本已加载、版本兼容且变量映射正确,否则运行时会报错。

43. webpack 分包的方式有哪些? ​

答案:

Webpack 分包主要有三类:通过多个 entry 拆出多页面或独立入口;使用 import() 做路由、功能级懒加载;用 optimization.splitChunks 抽取多个入口共享的依赖和体积较大的第三方包,并用 runtimeChunk 分离运行时代码。还可借助 externals 将库交给 CDN。分包不是越多越好,需要结合请求数、缓存命中率、首屏体积和 HTTP/2 能力设置缓存组与阈值。

44. 了解语义化版本 SemVer(Semantic Versioning)吗? ​

答案:

SemVer 用 主版本.次版本.补丁版本 表达兼容性变化:补丁版本修复兼容 bug,次版本在不破坏现有 API 的前提下增加功能,主版本表示不兼容变更。0.x 通常仍处于不稳定阶段,预发布版本如 1.2.0-beta.1 不等同于正式版;依赖范围中的 ^ 与 ~ 分别允许不同程度的自动升级。发布时应同步更新变更说明,并避免把破坏性改动伪装成小版本。

45. 说说你对 npx 的了解? ​

答案:

npx 是 npm 提供的执行包命令工具。它优先使用项目 node_modules/.bin 中的本地可执行文件,避免依赖全局安装;找不到时可临时获取并执行指定包,命令结束后通常不作为项目依赖保留。这样适合运行脚手架、一次性迁移和指定版本工具。生产或 CI 中应锁定版本,避免直接执行未审查的远程包,也要注意包脚本可能具有安装和执行副作用。

46. react native 工作原理是什么? ​

答案:

React Native 用 JavaScript 编写业务,通过 React 的组件树和状态更新生成原生视图,而不是把页面渲染成 DOM。JS 线程执行组件逻辑,渲染器将差异提交给 iOS、Android 原生组件;传统架构经 Bridge 序列化传递消息,新架构通过 JSI、Fabric 和 TurboModules 减少异步序列化开销。原生模块负责平台能力,性能瓶颈常在 JS 与原生通信、布局和列表渲染。

47. webpack 中 module、chunk 、bundle 的区别是什么? ​

答案:

module 是依赖图中的源模块,例如一个 JS、CSS 或图片文件;Webpack 读取并解析模块依赖。chunk 是构建过程中按入口、动态导入或分包规则形成的代码集合,是中间产物组织单位。bundle 是最终输出到磁盘、可被浏览器加载的文件,通常由 chunk 生成,一个 chunk 可能对应一个或多个 bundle。三者分别对应输入单元、构建分组和输出文件,不能简单视为同义词。

48. npm script 了解多少? ​

答案:

npm script 是 package.json 中 scripts 字段定义的命令别名,例如 npm run build。npm 执行时会把项目和依赖的 .bin 目录加入 PATH,因此可以直接调用本地工具;脚本可通过 npm run 查看,也支持参数传递、变量和前后置生命周期。应把构建、测试、Lint 等可重复流程写入脚本并固定入口,避免依赖开发者机器上的全局命令;复杂编排可交给专门任务工具。

49. npm lock 文件了解多少? ​

答案:

npm lock 文件记录一次安装解析后的确切依赖树,包括版本、下载地址、完整性校验和嵌套依赖关系。它让开发机、CI 与生产环境获得一致安装结果,减少上游小版本漂移和供应链风险。npm install 会根据 package.json 更新或生成 lock,CI 通常用 npm ci 严格按 lock 安装并清理已有依赖。升级依赖应通过 npm 命令修改并提交 lock,不能只手改版本字段。

50. npm script 生命周期有哪些? ​

答案:

npm 脚本支持通用的 pre<name>、<name>、post<name> 生命周期:执行 npm run build 前后会分别尝试执行 prebuild 和 postbuild。npm 还为安装、发布、测试等命令提供专用钩子,例如 preinstall、install、postinstall、prepublishOnly、preversion 和 postversion。需要注意 prepare 会在本地安装、打包等场景触发,安装依赖时应审查脚本,避免不必要的执行风险。

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

答案:

npm“幽灵依赖”是指代码能够直接 require 或 import 某个包,但它没有声明在当前项目的 package.json 中,通常是因为包被其他依赖扁平化到了顶层。这样本地可能能运行,换到不同 npm 版本、包管理器或生产安装方式就会失败,也会掩盖真实依赖边界。解决办法是把直接使用的包显式列为依赖,使用 lock 文件和依赖检查工具,并避免依赖安装目录的偶然结构。

52. 说说你对 Source Map 的了解 ​

答案:

Source Map 是源码与转换后代码之间的映射文件,记录压缩、转译或打包产物中的位置如何对应原始文件、行列和名称。浏览器开发者工具据此展示原始源码、断点和可读堆栈,错误监控也能还原线上异常。它不改变运行逻辑,但可能暴露源码和路径,因此生产环境应控制访问权限,常将 map 上传到监控平台而不公开部署,并确保发布版本与 map 严格匹配。

53. package.json 里面 sideEffects 属性的作用是什么? ​

答案:

package.json 的 sideEffects 用来声明模块是否可能在未被显式使用时仍产生副作用,例如注册全局样式、修改全局对象或执行初始化。Webpack 据此在 tree shaking 时决定能否安全跳过整个模块;设为 false 表示包内模块默认无副作用,也可用数组精确列出如 *.css 的例外。配置过宽会误删必要初始化,配置过保守则会增大产物,应按真实行为维护并测试生产构建。

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

答案:

Hash 路由把路由状态放在 URL 的 # 片段中。片段变化不会向服务器发起请求,浏览器刷新时仍可由同一个入口 HTML 启动 SPA,前端再监听 hashchange 完成页面切换,因此不需要服务端为每个前端路径配置回退。它兼容静态托管和较旧环境,但 URL 不够美观、搜索语义较弱,也不能直接获得 History API 的完整导航能力;新项目可按部署条件选择 history 路由。

55. 为什么 webpack 可以通过文件打包,让浏览器可以支持 CommonJS 规范? ​

答案:

Webpack 先从入口递归解析 require、import 等依赖,建立模块图,再把每个模块转换成带模块函数的记录,并在 bundle 中注入自己的模块表和加载器。运行时,模块通过包装函数获得 module、exports、require 等参数,首次调用后缓存导出结果;因此浏览器执行的是 Webpack runtime 模拟的 CommonJS,而不是浏览器原生识别了 CommonJS。代码分割时该机制还负责按 chunk 异步加载。

56. Webpack 中的 chunk 是什么? ​

答案:

Webpack 中的 chunk 是由模块依赖图切分出来的构建分组,常由入口、动态 import()、多个入口共享依赖或 SplitChunks 规则产生。它不是某个源文件,也不一定等于最终文件;Webpack 会基于 chunk 生成一个或多个 bundle,并附加 runtime、hash 等输出信息。异步 chunk 通常在运行时按需请求,首屏 chunk 则随入口加载。理解 chunk 边界有助于设计缓存、懒加载和首屏体积。

57. webpack tree-shaking 在什么情况下会失效? ​

答案:

tree-shaking 失效常见于使用 CommonJS 或动态不可静态分析的依赖,因为构建器无法可靠判断哪些导出未使用;模块存在顶层副作用、sideEffects 配置不准确、导出通过对象间接聚合,也会使模块被保留。开发模式下即使已标记未使用,代码也可能尚未真正删除,最终删除通常由压缩器完成。应优先使用 ES Module,正确声明副作用,并检查 production 产物和压缩配置。

58. 为什么现代前端应用需要打包工具进行打包编译? ​

答案:

现代前端需要打包工具,是因为源码往往包含浏览器不能直接执行或不适合直接部署的模块、TypeScript、JSX、CSS 预处理器和新语法。工具会解析依赖、转换语法、处理资源、拆分代码、压缩混淆并生成 hash 文件,还能做 tree shaking 和缓存优化。开发阶段则提供增量编译、热更新与错误定位。简单页面可以直接用原生 ES Module,但大型应用需要统一构建产物、兼容策略和部署流程。

59. 微前端的设计原则有哪些? ​

答案:

微前端的核心原则是按业务边界拆分、团队自治和独立交付,同时保证用户感知上仍是一体化应用。主应用负责壳、路由、权限和公共能力,子应用通过明确契约通信,避免共享可变全局状态;要隔离样式、JS 运行环境和资源,控制依赖重复与加载性能。还应统一登录、监控、错误处理和降级策略,支持版本兼容、灰度发布及子应用故障时的局部隔离。

60. 微前端中的路由加载流程是怎么样的? ​

答案:

微前端路由通常由主应用先匹配当前 URL,依据路由表确定目标子应用及入口地址;若子应用尚未加载,主应用拉取其资源并校验、缓存和初始化。子应用注册自己的路由,在获得挂载容器、公共依赖和上下文后渲染;切换路由时先卸载旧应用、清理监听和样式,再挂载新应用。浏览器前进后退、直接访问、预加载、加载失败与权限拦截都应由主子应用协同处理。

61. 如果有 git 仓库需要迁移,应该怎么操作? ​

答案:

迁移 Git 仓库前先备份并确认目标仓库为空或允许合并,然后在本地添加新的 remote,推送所有分支和标签,例如 git push new-origin --all 与 git push new-origin --tags;默认分支、保护规则、LFS 对象、子模块和 CI 密钥还需单独迁移。迁移后在目标平台核对提交数量、分支、标签、LFS 和构建结果,再切换协作者的 remote。若历史涉及敏感信息,应先清理历史并轮换凭据。

62. 有了解过 Protobuf 吗? ​

答案:

Protobuf 是 Google 的二进制序列化协议:用 .proto 文件定义消息结构和服务,再由 protoc 生成各语言代码。字段用编号编码,通常比 JSON 更紧凑、解析更快;通过新增字段、保留旧编号并避免复用,可实现向前、向后兼容。它适合 RPC 和高吞吐内部通信,但可读性和浏览器直接调试不如 JSON,协议变更必须遵循编号、默认值、枚举和删除字段的兼容规则。

63. 说说 Eslint 进行代码检查的原理 ​

答案:

ESLint 会先读取配置,按解析器把源码转换为 AST,再遍历 AST,调用规则对节点、作用域和引用关系进行检查。规则可以报告错误、给出定位和修复建议;开启 --fix 时,只有规则提供了安全的修复器才会改写源码。插件可扩展语法解析、规则和处理器,忽略文件与配置继承决定检查范围。它主要做静态规范与潜在问题检查,不等同于类型检查、格式化工具或运行时测试。

64. Webpack 有哪些常见配置? ​

答案:

Webpack 常见配置包括 entry 定义入口,output 定义文件名、目录和 publicPath,mode 选择开发或生产策略;module.rules 通过 loader 处理 JS、CSS、图片等资源,plugins 扩展 HTML 生成、环境变量和清理等能力。还会配置 resolve 的扩展名与别名、devServer、optimization 的压缩分包、devtool 的 source map,以及 externals。配置应围绕目标环境、缓存和产物体积取舍。

65. Webpack 怎么配置多入口应用, 并实现公共依赖的提取? ​

答案:

用 entry 配置多个入口,例如 { app: './src/app.ts', admin: './src/admin.ts' },再以 [name]/js/[name].[contenthash].js 输出。公共依赖可通过 optimization.splitChunks.cacheGroups 提取到 vendors,运行时代码用 runtimeChunk: 'single' 单独生成;多个入口共享模块时要确认拆包结果和 HTML 的引用关系。

66. Webpack 中的运行时 chunk 是什么?在项目工程中, 如何打包和加载这个运行时 chunk ? ​

答案:

运行时 chunk 是 Webpack 注入的模块加载器和 manifest,记录模块 ID、异步 chunk 文件名及加载方法,本身通常不包含业务模块。配置 optimization.runtimeChunk: 'single' 可让多个入口共享它,再用 HtmlWebpackPlugin 自动把 runtime、业务入口和异步文件按依赖顺序注入 HTML;生产环境应给它设置稳定的内容哈希并配合长期缓存。

67. 一个使用了 Webpack 的前端项目,该怎么配置,实现使用 babel-loader 来编译 tsx 文件? ​

答案:

安装 babel-loader、@babel/core、@babel/preset-env、@babel/preset-typescript 和 React 对应 preset,在 Webpack 中匹配 /\.tsx?$/ 并排除 node_modules:use: { loader: 'babel-loader', options: { presets: [['@babel/preset-env',{targets:'defaults'}], '@babel/preset-react', '@babel/preset-typescript'] } }。同时把 resolve.extensions 配为 ['.tsx','.ts','.jsx','.js'];TypeScript preset 只负责去类型,类型检查需另用 tsc --noEmit。

68. vite 使用了哪些编译器,分别有什么作用? ​

答案:

Vite 开发阶段主要使用 esbuild 做依赖预构建和常规 JS/TS 转换,速度来自 Go 实现;框架文件由对应编译器处理,例如 Vue 用 @vue/compiler-sfc、React JSX 通常由 esbuild 或插件转换。生产构建默认由 Rollup 负责模块图、插件流水线、代码分割和输出优化,CSS、资源等则由 Vite 内置插件及框架插件协同处理。

69. esbuild 和 rollup 都是 vite 的基础依赖,它们有什么不同呢? ​

答案:

esbuild 是高性能的编译器和打包器,擅长依赖预构建、TS/JSX 转换和压缩,但插件与复杂输出控制相对有限。Rollup 以 ESM 模块图为核心,插件体系和 tree-shaking、代码分割、库输出能力更强,适合生产构建。Vite 通常用 esbuild 加速开发,用 Rollup 组织生产打包;二者不是完全互斥的替代品。

70. vite 和 webpack 在热更新的实现上有什么区别? ​

答案:

Webpack 开发时通常先建立较完整的依赖图,文件变更后由编译器定位受影响模块,通过 HMR runtime 向浏览器推送更新;依赖关系变化时可能触发更大范围重编译。Vite 开发阶段直接利用浏览器原生 ESM,修改模块后只重新转换该模块,并通过 WebSocket 发送 HMR 消息,边界由模块的 import.meta.hot 接受逻辑决定,因此大型项目启动和局部更新通常更快。

71. webpack 中的 webpack-dev-server 有什么作用? ​

答案:

webpack-dev-server 是开发服务器:把 Webpack 构建结果放在内存中提供 HTTP 访问,支持静态文件、代理、历史路由回退、自动刷新和 HMR。它监听源码变化并通知浏览器更新,避免每次手动执行构建和刷新页面。它服务的是开发体验,实际项目仍应通过 Webpack production mode 生成可部署的静态产物。

72. webpack 中的 webpack-dev-server 为什么不适用于线上环境? ​

答案:

webpack-dev-server 面向本地开发,产物主要驻留内存,依赖开发服务器进程、HMR 通道和调试能力;它没有生产部署需要的静态文件托管、进程治理、缓存策略、监控和安全边界。线上应先执行 webpack --mode production 生成带哈希的静态资源,再交给 Nginx、CDN 或对象存储,并配置压缩、缓存和回退规则。

73. 对于一个使用 Webpack 的项目,如果需要直接通过 script 标签引入第三方的资源,应该怎么处理? ​

答案:

不能把这类脚本当作 Webpack 模块直接打包。可在 HTML 模板中用 HtmlWebpackPlugin 的 template 写入 <script src="https://cdn.example.com/lib.js"></script>,并在代码中通过库暴露的全局变量使用;若希望依赖由构建器解析,则改用 import。对 CDN 资源应固定版本、设置 SRI 和 CSP,并在 externals 中声明以避免重复打包。

74. 在Babel里,stage0、stage1、stage2 和 stage3 分别代表什么含义? ​

答案:

Babel 旧的 stage 机制表示 ECMAScript 提案成熟度:stage 0 是想法展示,stage 1 是提出方案,stage 2 已形成初步规范,stage 3 规范基本稳定并等待实现反馈,stage 4 才是正式完成。它们不是浏览器兼容等级,也不能保证语法最终进入标准。现代 Babel 已移除这些 preset,应按具体插件或 preset-env 配置。

75. babel 的核心库有哪些? ​

答案:

Babel 的核心通常包括 @babel/core(解析、转换、生成代码的编译引擎)、@babel/parser(把源码解析成 AST)、@babel/traverse(遍历和修改 AST)、@babel/types(创建和判断 AST 节点)以及 @babel/generator(由 AST 生成代码)。@babel/preset-env、@babel/preset-react 等是预设,@babel/plugin-transform-* 是具体转换插件。

76. webpack 在打包时,给生成的文件名上添加的 hash 码,是如何生成的? ​

答案:

Webpack 的哈希由构建内容计算,具体取决于占位符:[hash] 通常关联整个构建,任一模块变化可能导致整体变化;[chunkhash] 关联 chunk 内容;[contenthash] 只关联该资源内容,更适合长期缓存。Webpack 会根据依赖图和模块内容生成哈希,并受模块 ID、运行时代码和压缩结果影响,所以应配合稳定 ID、独立 runtime 和 contenthash 减少无关失效。

77. Husky 和 lint-staged 有什么区别? ​

答案:

Husky 负责安装并管理 Git hooks,在 pre-commit、commit-msg、pre-push 等生命周期触发命令;lint-staged 负责筛选当前暂存区文件,并把这些文件传给 ESLint、Prettier 等任务。常见组合是 Husky 的 pre-commit 调用 lint-staged,commit-msg 调用提交信息校验。前者是触发机制,后者是暂存文件任务编排。

78. babel-runtime 有什么作用? ​

答案:

@babel/runtime 提供 Babel 转换代码需要的辅助函数和按需 polyfill,例如类继承、对象展开等,避免每个文件都重复注入同一份 helper。配合 @babel/plugin-transform-runtime,转换结果改为引用 runtime 模块,能减小重复代码并避免污染全局。它是运行时依赖,应放在 dependencies;若启用 core-js 选项,还要匹配安装对应 runtime 版本。

79. 怎么实现 commit lint? ​

答案:

先安装 @commitlint/cli 和约定配置,例如 @commitlint/config-conventional,在 commitlint.config.js 中 extends 该配置;再用 Husky 注册 commit-msg hook:npx commitlint --edit "$1"。提交信息按 type(scope): subject 编写,如 feat: add search。CI 中也应执行校验,防止绕过本地 hook;规则可按团队发布流程调整。

80. 如何清理源码里面没有被使用的代码?主要是 JS、TS、CSS 代码 ​

答案:

JS/TS 先用 ESLint、TypeScript 编译器和 IDE 引用分析找未使用变量、导出和文件,再结合构建产物、覆盖率和依赖分析确认动态引用,避免误删反射或字符串加载的代码。CSS 可用 PurgeCSS、构建器的 unused CSS 检测,并保留全局样式、动态 class、主题和第三方选择器白名单。删除前应做测试、路由遍历和产物回归,工具结果不能代替运行验证。

81. package.json 文件中的 devDependencies 和 dependencies 对象有什么区别? ​

答案:

dependencies 放线上运行时需要的包,例如框架、请求库和服务端运行库;devDependencies 放开发、测试、类型检查、构建和代码规范工具。生产安装常用 npm ci --omit=dev,因此构建时需要的包应在构建阶段可用,运行时仍会被代码加载的包必须放 dependencies。最终以项目实际部署方式判断,不能简单按“是否会被打包”分类。

82. webpack的module、bundle、chunk分别指的是什么? ​

答案:

module 是 Webpack 处理的单个资源或文件,经 loader 转换后进入依赖图;chunk 是构建阶段按入口、动态 import() 或拆包规则组织的一组模块,是逻辑代码块;bundle 是 chunk 输出后的实际文件。三者不是严格一一对应:一个 chunk 可能输出多个资源,多个 chunk 也可能因运行时、CSS 或资源类型产生不同 bundle。

83. 说一下 vite 的构建流程 ​

答案:

Vite 生产构建先读取配置和入口,解析模块及插件,必要时由 esbuild 转换 TypeScript、JSX 等语法;随后把模块交给 Rollup 建立依赖图,执行 tree-shaking、代码分割、公共依赖抽取和插件钩子,最后处理 CSS、静态资源、manifest 并输出文件。开发服务器是按请求转换 ESM,和生产阶段的完整打包流程要区分开。

84. webpack 5 的主要升级点有哪些? ​

答案:

Webpack 5 的重要变化包括持久化文件缓存、改进的 tree-shaking 和模块/ chunk ID、原生 Module Federation、改进的 splitChunks、更完善的 WebAssembly 与动态 import 支持,以及默认运行时和资源处理的优化。它还移除了 Node.js 核心模块自动 polyfill 等旧行为。升级时要检查 loader、插件兼容性、polyfill、缓存目录和废弃配置,不能只按新特性清单迁移。

85. git 中 rebase、reset、revert 有什么区别? ​

答案:

rebase 把提交重新应用到新基点,会改写提交 ID,适合整理尚未共享的分支;共享分支上使用需谨慎。reset 移动当前分支指针,可选 --soft、--mixed、--hard 改变暂存区和工作区,适合本地撤回,--hard 会丢未保存内容。revert 新建一个反向提交抵消旧提交,不改写公共历史,适合已推送分支。

86. 简单说下你对 Electron 架构的理解 ​

答案:

Electron 由主进程、渲染进程和预加载脚本组成,并把 Chromium、Node.js 与原生能力封装在一起。主进程管理应用生命周期、窗口和系统资源;每个窗口通常对应独立渲染进程,负责页面 UI;preload 在隔离边界内通过 contextBridge 暴露白名单 API,再用 IPC 通信。生产环境应开启 contextIsolation、关闭渲染进程 Node 权限并校验 IPC 参数。

87. 为什么小程序里拿不到dom相关的api? ​

答案:

小程序页面不是运行在完整浏览器文档环境中,框架用受控的视图层和逻辑层管理页面,因此没有开放 window、document、DOM 节点和任意脚本操作能力。这样可以隔离运行环境、统一跨平台渲染并便于性能和安全管控。开发者应使用组件、数据绑定、选择器查询和官方 API;需要原生能力时只能通过平台提供的接口申请。

88. 小程序的双线程分别做的什么事情? ​

答案:

小程序通常把逻辑层和视图层分开:逻辑层运行 JavaScript,负责状态、事件处理和调用 API;视图层负责模板解析、组件布局与渲染。两者通过异步消息和数据更新通信,逻辑层不能直接操作视图层 DOM,setData 会把变化传给视图层。因此应减少频繁、大对象的数据传输,把高频动画和交互尽量交给视图层或原生组件。

89. 说说微信小程序的架构? ​

答案:

微信小程序由逻辑层、视图层和底层客户端组成:逻辑层运行 JS,维护页面状态并调用 wx API;视图层用 WXML 和 WXSS 描述组件与样式;客户端负责渲染、网络、存储及原生能力,并通过桥接通信。页面以 App、Page、Component 的生命周期组织,配置由 app.json 等文件管理。由于逻辑与视图隔离,更新应通过数据驱动而不是直接改 DOM。

90. Electron 中的主进程和渲染进程分别是什么? ​

答案:

Electron 主进程通常只有一个,运行 Node.js,负责应用生命周期、窗口创建、菜单、文件系统等权限较高的工作;渲染进程由窗口创建,运行 Chromium 页面并负责 UI,默认不应直接接触 Node。preload 在两者之间提供受控桥接,渲染进程通过 contextBridge 暴露的 API 调用 IPC,主进程用 ipcMain 接收并返回结果,边界上要做权限和参数校验。

91. Electron 有哪些特点和优势? ​

答案:

Electron 用 HTML、CSS、JavaScript 构建 Windows、macOS、Linux 桌面应用,能复用 Web 生态和团队技能,并通过 Node.js 访问文件、进程、系统通知等能力。它的优势是跨平台、开发迭代快、生态成熟、UI 自由度高;代价是随应用打包 Chromium 和 Node,内存与安装体积通常高于原生应用,性能和安全需要额外治理。

92. 说说你对前端工程化的理解 ​

答案:

前端工程化是用可重复的规范、工具和自动化流程管理需求落地、开发、测试、构建、发布与监控,而不只是引入打包器。核心包括模块化与依赖管理、类型和代码规范、自动化测试、构建优化、环境配置、CI/CD、版本与质量门禁。目标是让协作可预测、问题可追踪、交付可回滚,并根据项目规模控制工具复杂度,避免为流程而流程。

93. 聊聊 vite 和 webpack 的区别 ​

答案:

Vite 开发时利用浏览器原生 ESM,按请求转换模块,依赖预构建用 esbuild,因此启动和局部 HMR 通常更快;生产构建默认交给 Rollup,配置和插件模型也更贴近现代 ESM。Webpack 开发和生产都以依赖图打包为中心,loader、plugin 生态和兼容历史项目能力更完整。Vite 更轻快,Webpack 更成熟灵活,选择取决于生态、定制需求和迁移成本。

94. 什么是 CI/CD? ​

答案:

CI 是持续集成:代码提交后自动安装依赖、检查、构建和测试,尽早发现冲突与回归。CD 有两种常见含义:持续交付把通过验证的版本自动部署到可发布环境,生产发布通常需人工批准;持续部署则连生产发布也自动完成。实践中还应加入制品留存、环境隔离、密钥管理、灰度或回滚、权限审批和失败通知,不能只理解为一条自动部署脚本。

95. 说说 jsBridge 的原理 ​

答案:

JSBridge 是 WebView 中 JavaScript 与 Native 之间的受控 RPC 通道:一端把方法名、参数和回调 ID 序列化,另一端接收、校验并执行,再按 ID 返回结果或错误。实现可使用平台消息接口,也可通过拦截约定 URL、注入脚本等方式传递消息。设计时要处理异步回调、超时、版本兼容、数据大小和生命周期,并只暴露白名单能力,避免任意调用原生权限。

96. 你对 babel 了解吗,能不能说说几个 stage 代表什么意思? ​

答案:

Babel 是把现代 JavaScript 语法转换为目标环境可执行代码的编译器,通常由 parser、插件遍历 AST、generator 和 preset 组成。旧 stage 表示提案成熟度:Stage 0 是想法,Stage 1 是方案,Stage 2 有初步规范,Stage 3 规范基本稳定,Stage 4 已完成并纳入标准。stage preset 已被移除,现代项目应使用明确插件或 preset-env,并区分语法转换与 polyfill。

97. 说说 webpack-dev-server 的原理 ​

答案:

webpack-dev-server 启动 webpack 编译,并把产物放在内存文件系统中,通过 HTTP 提供访问。它监听源码变化,重新编译后用 WebSocket 通知浏览器;普通 live reload 刷新页面,启用 HMR 时则只替换可接受的模块及其依赖。它只适合开发环境,生产环境应使用正式静态服务器。

98. 说说你对 Electron 的了解 ​

答案:

Electron 用 Chromium 渲染界面、用 Node.js 提供文件系统等系统能力,主进程负责窗口和原生生命周期,渲染进程负责页面。两者通常通过 preload 暴露受控的 contextBridge API 通信。它跨平台、复用前端生态,但包体、内存和启动成本较高,生产应用还要注意进程隔离、权限和自动更新安全。

99. 说说你对flutter的了解 ​

答案:

Flutter 是 Google 的跨平台 UI 框架,使用 Dart 编写,Framework 提供 Widget、布局、手势和动画,Engine 通常借助 Skia 自绘 UI,再由 Embedder 适配各平台。自绘使多端表现一致、减少 JS 与原生往返;Dart 支持 JIT 热重载和 AOT 发布。代价是包体较大、需维护 Dart 生态,平台能力仍要通过插件或原生代码接入。

100. 说说你对React Native的了解? ​

答案:

React Native 用 React 描述界面,JavaScript 逻辑通过运行时与原生模块通信,并映射为 Android、iOS 的原生组件,因此能复用业务代码并保留部分原生体验。它适合表单、内容和业务型应用,但桥接或线程通信会增加开销,复杂动画、图形和平台特性常需原生模块;新架构通过 JSI、Fabric、TurboModules 减少这类成本。

101. 前端领域有哪些跨端方案? ​

答案:

跨端方案可按“跨逻辑”和“跨渲染”区分:WebView/H5 方案成本低但性能和系统能力受限;React Native、Weex 以 JS 逻辑驱动原生组件;Flutter 以 Dart 和自绘引擎统一渲染;小程序运行在宿主沙箱中;还可用 Taro、uni-app 等编译型方案,或团队自研引擎。选择要结合性能、平台覆盖、原生能力、团队技术栈和维护成本。

102. 说说你对跨平台的理解 ​

答案:

跨平台是用一套或大部分共享代码,适配多个操作系统或设备;跨端还涉及 Web、移动端、桌面端和小程序等不同运行容器。它通常只能共享业务逻辑、组件抽象或渲染层,平台差异并不会消失,权限、导航、支付和性能热点仍需适配。评估方案时应比较复用率、体验、包体、调试效率、生态和原生扩展成本。

103. webpack loader 和 plugin 实现原理 ​

答案:

Loader 面向单个模块资源,在模块被解析时按配置链式执行,接收源内容并返回转换后的内容或 AST,常用于转译、预处理和资源导入;通常要声明匹配规则、缓存和 source map。Plugin 面向整个编译生命周期,通过 compiler、compilation 等钩子参与入口、模块、资源生成和完成阶段,可注册自定义插件或修改构建流程。

104. 说说你对低代码的了解 ​

答案:

低代码是用可视化配置、组件编排和少量脚本生成应用的开发方式,平台通常包含物料组件、页面编辑器、数据源与流程编排、代码生成或运行时引擎。它适合表单、后台和流程类重复场景,可提升交付速度;复杂交互、极致性能和强定制产品仍需专业开发。关键风险是抽象受限、生成物可维护性、版本治理和平台锁定。

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

答案:

npm 和 Yarn 都是 Node.js 包管理器,都能解析 package.json、安装依赖并运行脚本。差异主要在锁文件和安装实现:npm 使用 package-lock.json,Yarn 使用 yarn.lock,历史版本在并行安装、缓存和离线能力上不同。现在 npm 新版本也支持工作区,Yarn 分为不同版本且 Plug'n'Play 等行为可能影响兼容性;团队应固定版本并只保留一种锁文件。

106. 如何迁移仓库,同时保留原有的提交记录和分支? ​

答案:

迁移整个仓库可直接迁移裸仓库:先在源仓库执行 git clone --bare old-url repo.git,再在新远程执行 git push --mirror new-url,这样会保留所有分支、标签和提交记录。若只迁移部分分支,可普通 clone 后逐个 push。迁移后检查默认分支、保护规则、LFS、子模块和 CI 配置;--mirror 会同步删除目标端多余引用,使用前要确认目标仓库为空或可覆盖。

107. 说说你对git reset 和 git revert 的理解?区别? ​

答案:

git reset 移动当前分支和 HEAD,可选择 --soft 保留暂存区、--mixed 保留工作区但取消暂存、--hard 连工作区也回退,因此适合整理本地未推送提交,误用 hard 可能丢数据。git revert 不改历史,而是创建一个反向提交抵消指定提交,适合共享分支和已推送历史。核心区别是 reset 重写指针,revert 追加可审计历史。

108. 说说 git 发生冲突的场景?如何解决? ​

答案:

冲突通常发生在两个分支修改同一文件同一区域、文件一方删除另一方修改,或 rebase/cherry-pick 无法自动合并时。先用 git status 定位冲突,打开文件处理 <<<<<<< 等标记,确认结果后 git add,再执行 git commit;rebase 中用 git rebase --continue,也可 --abort 放弃。解决后应运行测试并检查差异,不能只依赖 Git 的自动选择。

109. 说说你对git rebase 和 git merge的理解?以及它们的区别? ​

答案:

merge 把两个历史合并,必要时生成合并提交,保留真实分支拓扑,适合共享分支。rebase 将当前分支提交重新接到目标分支最新提交之后,形成更线性的历史,但会重写这些提交的哈希,不应随意用于他人已基于其开发的公共分支。merge 冲突通常一次解决,rebase 可能逐个提交解决;发布前可 rebase 整理私有分支,合入公共分支要谨慎。

110. 说说你对git stash 的理解?应用场景? ​

答案:

git stash 把工作区和暂存区的未提交修改保存为栈中条目,并恢复到干净状态,适合临时切分支、拉取紧急修复或切换任务。stash push -u 可包含未跟踪文件,stash pop 应用后删除条目,apply 应用但保留条目,list/show/drop 用于管理。它不是长期备份,重要工作应提交到临时分支;应用时仍可能产生冲突。

111. 说说对git pull 和 git fetch 的理解?有什么区别? ​

答案:

git fetch 只从远程下载对象并更新远程跟踪分支,如 origin/main,不会改动当前分支、工作区或索引,适合先查看差异。git pull 默认等价于 fetch 加 merge,也可配置为 rebase,会直接整合到当前分支,可能产生冲突。团队应明确 pull 策略;需要可控审查时先 fetch,再手动 merge 或 rebase。

112. 说说Git 中 HEAD、工作树和索引之间的区别? ​

答案:

HEAD 是当前检出位置的符号引用,通常指向当前分支,分支再指向某个提交;工作树是磁盘上正在编辑的文件;索引也叫暂存区,保存下一次提交的快照。git add 把工作树变化放入索引,git commit 用索引生成提交并移动分支/HEAD,git restore 可在工作树与索引之间恢复。三者不同步时,git status 会分别报告差异。

113. 说说Git常用的命令有哪些? ​

答案:

常用命令可按用途记忆:git init/clone 建库,status/diff/log/show 查看状态和历史,add/commit 记录修改,branch/switch 管理与切换分支,merge/rebase/cherry-pick 整合提交,fetch/pull/push 同步远程,stash 临时保存修改,tag 标记版本,revert/reset/restore 回退内容。涉及共享历史时要先确认命令是否会重写提交。

114. 说说Git中 fork, clone,branch这三个概念,有什么区别? ​

答案:

fork 是托管平台上的操作,在服务器端复制一个仓库到自己的账号下,常用于无直接写权限时提交 Pull Request;clone 是把某个远程仓库的对象和工作副本下载到本地;branch 是同一仓库内指向提交的轻量可移动引用,用于隔离开发线。fork 解决权限与协作边界,clone 解决本地副本,branch 解决版本并行。

115. 说说你对Git的理解? ​

答案:

Git 是分布式版本控制系统,以提交构成有向无环历史,分支和标签只是指向提交的引用。每个开发者本地都有完整历史,因此提交、查看和分支操作通常不依赖网络,再通过远程仓库协作。它支持分支合并、rebase、冲突处理、提交审计和回滚;团队实践中还应配合规范提交、代码评审、保护分支与自动化检查。

116. 说说你对版本管理的理解? ​

答案:

版本管理是对代码及相关配置的变更进行记录、比较、协作和恢复。它能追踪谁在何时为何修改,支持分支并行、合并冲突、发布标记和问题回退。现代团队通常用 Git 管理历史,以提交作为可审计单元,再结合远程仓库、代码评审、CI 和语义化版本。提交应小而完整,避免把构建产物、密钥等不应追踪的内容纳入仓库。

117. 如何检查Javascript中的内存泄漏? ​

答案:

先用 Chrome DevTools 的 Memory 记录 Heap Snapshot,执行一轮可重复操作后强制 GC,再比较快照中的对象数量、Retained Size 和引用链;也可用 Allocation instrumentation 查找持续增长的分配。常见原因是未移除的事件监听、定时器、Observer、闭包引用、全局缓存和 DOM 脱离文档。修复时在销毁阶段解除订阅并清理引用,最后用多轮快照回归确认。

118. webpack treeShaking机制的原理是什么? ​

答案:

Tree Shaking 利用 ES Module 的静态 import/export 结构分析依赖图,标记从入口不可达或未被使用的导出,并在压缩阶段删除死代码。它依赖静态模块、正确的 sideEffects 配置和生产压缩;CommonJS 的动态 require 难以可靠分析。带副作用的模块不能盲删,应在 package.json 声明 sideEffects,同时避免把初始化、全局注册等副作用误标为可消除。

119. 谈谈对 babel-polyfill 的了解 ​

答案:

babel-polyfill 的作用是为不支持新 JavaScript API 的运行环境补齐能力,例如 Promise、Symbol、Map、Array.from 和 Object.assign;Babel 本身主要转换语法,不能凭空实现这些运行时 API。旧方案通常全量引入,会污染全局且体积较大;现代 Babel 更常用 core-js 配合 @babel/preset-env 的 useBuiltIns 按目标环境按需注入,库开发还应考虑避免污染宿主。

120. babel 和 babel ployfill 有什么关系? ​

答案:

Babel 是编译转换工具,主要把箭头函数、类、可选链等新语法转换成目标环境能解析的语法;polyfill 是运行时垫片,为旧环境补充 Promise、Map、Array.from 等缺失 API。语法转换和 API 补齐是两件事,前者不能解决后者。实际项目可用 @babel/preset-env 配合 core-js 按浏览器目标注入垫片,并注意全局污染和体积。

121. ESLint 是什么? ​

答案:

ESLint 是基于解析器和 AST 的 JavaScript/TypeScript 静态检查工具。它按配置规则检查语法错误、潜在缺陷和团队风格,并可通过插件支持 React、Vue 等场景,部分规则能自动修复。它不能替代类型检查、测试或运行时监控;工程中通常在编辑器、提交钩子和 CI 中统一配置版本与规则,避免本地和流水线结果不一致。

122. babel-polyfill 有什么用? ​

答案:

Babel 默认转换语法,不会实现运行环境缺少的内置对象和方法,例如 Promise、Symbol、Map、Proxy、Array.from 或 Object.assign。babel-polyfill 通过引入 core-js 等实现这些 API 的垫片,让旧浏览器能运行相应代码。它是运行时兼容方案,不是语法编译;旧版本多为全量注入,现代项目应按 browserslist 目标按需引入以控制体积。

123. 简述微信小程序原理? ​

答案:

微信小程序将逻辑层与渲染层分离:逻辑层运行 JavaScript,负责状态、事件和请求;渲染层根据 WXML/WXSS 展示页面,二者通过宿主 Native 层通信并同步数据,因此不能像网页那样直接操作 DOM。部分 map、canvas 等组件由原生实现。发布包、域名、权限和分包受平台约束,频繁的大对象 setData 会增加序列化和通信成本,应按需更新数据。

124. 微信小程序的优劣势? ​

答案:

优势是无需安装、入口多、依托微信登录支付和社交生态,开发与分发成本通常低于独立 App,适合服务、营销和轻量业务。劣势是运行在平台沙箱中,能力、包体、域名和跳转受限制,样式与系统级能力不如原生自由;用户留存依赖平台入口,更新还受审核和版本策略影响。因此它适合高频轻交互,不一定适合重性能或强离线应用。

125. 小程序页面间有哪些传递数据的方法? ​

答案:

少量、可序列化参数可随 navigateTo URL 传递,在目标页 onLoad 获取;页面间双向通信可使用 EventChannel。跨多个页面共享的状态可放在 App.globalData 或状态管理模块,但要自行触发页面同步;需要持久化的配置和缓存可用 wx.setStorage/getStorage。选择时要考虑数据大小、生命周期、响应式需求和敏感信息,避免把大对象或密钥塞进 URL。

126. 微信小程序bindtap 和 catchtap 区别? ​

答案:

两者都是小程序的点击事件绑定。bindtap 触发当前节点处理函数后,事件仍会向父节点冒泡,父级同名监听也可能执行;catchtap 在当前节点处理时阻止事件继续冒泡。需要父子节点都响应时用 bindtap,需要点击子元素不触发父级代理逻辑时用 catchtap。它们都不等同于阻止默认行为,是否阻止默认行为要看具体事件和组件。

127. 小程序 WXSS 与 CSS 的区别? ​

答案:

WXSS 是小程序的样式语言,语法接近 CSS,但由小程序运行时解析并受其组件体系和安全边界约束。它增加了适配屏幕的 rpx 单位,支持页面与组件的样式隔离,选择器、继承和外链能力也不等同于浏览器 CSS;不能直接依赖 DOM 或任意全局样式。WXSS 适合小程序渲染环境,跨端项目需以目标平台实际支持范围为准。

128. 简述一下微信小程序的主要文件有哪些? ​

答案:

全局文件通常是 app.js、app.json 和可选的 app.wxss:分别负责小程序生命周期与全局逻辑、页面路由及窗口/tabBar 等配置、全局样式。每个页面通常由同名的 .js、.json、.wxml、.wxss 组成,分别处理逻辑、局部配置、结构和样式;自定义组件也采用类似四文件结构。project.config.json 是开发工具配置,sitemap.json 等属于可选文件。

129. 怎么进行移动端的调试? ​

答案:

移动端调试通常分三层:页面内用 vConsole 查看 Console、DOM、Network 和存储;网络问题用 Charles 等代理抓包,配置手机与电脑同一 Wi-Fi、代理及 HTTPS 证书;WebView 则用 Android Chrome DevTools 的 inspect,或 macOS Safari 的“开发”菜单连接 iOS。真机还要重点检查 viewport、触摸事件、权限、弱网和不同系统内核。

130. 前后端分离是什么? ​

答案:

前后端分离是前端与后端独立开发、独立部署:前端产出 HTML、CSS、JS 静态资源,通过 HTTP API 获取数据;后端只负责业务、数据和接口。双方以接口契约协作,前端可用 Mock 并行开发,前端和后端也能分别发布、扩容。代价是要处理 CORS、认证、版本兼容、错误约定和跨域部署等问题。

131. 数据Mock是什么? ​

答案:

Mock 是在真实后端接口未就绪、不可用或需要稳定测试时,按约定的接口结构模拟响应数据和部分业务行为。它能让前后端并行开发,也便于覆盖空数据、异常、分页等场景。实践中应尽量复用 OpenAPI 或接口类型定义,区分开发 Mock、自动化测试 Stub 与线上真实服务,避免假数据长期偏离契约。

132. 怎么使用 git 将多次提交压缩成一次提交? ​

答案:

先确认工作区干净,并用 git log 找到要合并的提交数量。例如合并最近 3 个提交:git rebase -i HEAD~3,保留最早一条为 pick,其余改为 squash 或 fixup,保存后编辑最终提交说明。冲突时解决文件后执行 git add、git rebase --continue;放弃则 git rebase --abort。已推送分支需要协作确认,并通常用 --force-with-lease 更新远端。

133. 什么是 git stash? ​

答案:

git stash 用于临时保存当前未提交的工作区和暂存区修改,使工作树恢复干净,方便切换分支或处理紧急任务。常用 git stash push -u -m "说明" 保存(-u 包含未跟踪文件),git stash list 查看,git stash pop 恢复并删除记录,git stash apply 恢复但保留记录。它不是正式提交,重要工作仍应提交到分支。

134. git pull 和 git fetch 有什么区别? ​

答案:

git fetch 只从远端获取对象和分支指针,更新如 origin/main,不会改动当前分支、工作区或自动合并,适合先审查差异。git pull 默认等价于 git fetch 加 git merge,也可配置为 fetch 加 rebase,会进一步整合远端提交并可能产生冲突。因此团队协作中常先 fetch,再明确选择 merge 或 rebase。

135. Git,GitHub与GitLab分别是什么?有什么区别? ​

答案:

Git 是分布式版本控制系统,负责记录提交、分支、合并和协作历史;GitHub 是基于 Git 的云端代码托管与协作平台,提供 Pull Request、Issue、Actions 等服务;GitLab 也是托管与协作平台,常见优势是可自建、权限和 CI/CD 集成较完整。三者不是同一层概念:Git 是工具,GitHub/GitLab 是服务产品,具体选型取决于开源生态、合规和部署要求。

136. 与webpack类似的工具还有哪些?区别? ​

答案:

常见替代工具有 Rollup、Parcel、Vite,以及偏任务编排的 Gulp、Grunt。Webpack 生态成熟、Loader/Plugin 强,适合复杂应用、代码分割和兼容性处理;Rollup 以 ESM 和 Tree Shaking 见长,适合库打包;Parcel 零配置、上手快,但深度定制和生态通常弱些;Vite 开发期利用原生 ESM 按需加载,构建常用 Rollup,适合现代应用。

137. 如何提高webpack的构建速度? ​

答案:

先用构建分析工具定位瓶颈,再针对性优化:开启持久化缓存,缩小 Loader 的 include/exclude 范围;用 thread-loader 处理确有收益的重型任务;复用不常变的依赖,必要时使用 externals;合理配置 Babel,避免编译 node_modules;开发环境启用增量构建、减少 Source Map 成本。Webpack 5 通常优先使用 cache.type: 'filesystem',并用 profile 验证收益。

138. 说说如何借助webpack来优化前端性能? ​

答案:

Webpack 优化前端性能主要从产物、加载和缓存入手:按路由或功能动态导入做代码分割和懒加载,用 Tree Shaking、压缩和 CSS 提取减少体积;通过 splitChunks 抽离稳定公共依赖;配置内容哈希实现长期缓存;图片和字体按需压缩、选择合适格式;结合 Bundle Analyzer 清理重复或过大的依赖。首屏关键资源应预加载,非关键资源延后加载。

139. 说说webpack proxy工作原理?为什么能解决跨域? ​

答案:

开发时浏览器访问前端开发服务器,前端服务器根据 devServer.proxy 把匹配路径转发给后端,并可重写路径、设置目标 Host 或 HTTPS。浏览器看到的请求仍是同源前端地址,跨域发生在服务器之间,因此绕过了浏览器的同源限制。它只解决开发期代理,不是生产 CORS 方案;生产环境通常由 Nginx、网关或后端统一配置转发与跨域策略。

140. 说说webpack的热更新是如何做到的?原理是什么? ​

答案:

HMR 的流程是:Webpack 监听文件变化并在内存中增量编译,开发服务器通过 WebSocket 将新构建的 hash 和状态通知浏览器;客户端据此请求更新清单和模块代码,再由 HMR runtime 找到受影响模块。若模块或其依赖声明了 module.hot.accept,就替换模块并尽量保留状态;无法安全接受时回退为整页刷新。静态资源变化触发的 live reload 与 HMR 不同。

141. 面试官:说说Loader和Plugin的区别?编写Loader,Plugin的思路? ​

答案:

Loader 是模块转换函数,接收资源内容并返回转换后的内容或 AST,配置在 module.rules,适合把 TypeScript、Sass、图片等变成 Webpack 可处理的模块。Plugin 面向整个构建生命周期,通过 compiler/compilation hooks 参与模块解析、资源生成或输出,配置在 plugins。自定义 Loader 通常导出同步函数或调用 this.async() 异步返回;Plugin 则实现 apply(compiler),在合适 Hook 注册逻辑。

142. 说说webpack中常见的Plugin?解决了什么问题? ​

答案:

常见 Plugin 包括 HtmlWebpackPlugin(生成 HTML 并自动注入资源)、DefinePlugin 或 EnvironmentPlugin(注入构建期常量)、MiniCssExtractPlugin(抽离 CSS)、CopyWebpackPlugin(复制静态文件)、HotModuleReplacementPlugin(HMR)、MiniCssExtractPlugin 配合压缩插件优化样式,以及 webpack-bundle-analyzer(分析产物体积)。具体选择应以当前 Webpack 版本和项目目标为准。

143. 说说webpack中常见的Loader?解决了什么问题? ​

答案:

常见 Loader 有 babel-loader(按 Babel 配置转换 JS/TS)、ts-loader 或 swc-loader(处理 TypeScript)、css-loader(解析 CSS 依赖)、style-loader(将 CSS 注入页面)、sass-loader(编译 Sass)、postcss-loader(执行 PostCSS)、file-loader/资源模块(处理图片字体等)。Loader 按规则匹配文件并可串联,执行顺序通常从右到左;配置 include/exclude 可避免误处理依赖。

144. 说说webpack的构建流程? ​

答案:

Webpack 构建大致经历:读取并合并配置,创建 Compiler、加载插件;依据 entry 确定入口;从入口递归解析依赖,按规则交给 Loader 转换,形成模块依赖图;将模块组织成 Chunk;插件在生命周期中修改或生成资源;最后根据 output 写出文件。开发模式可能把资源放在内存中,具体 Hook 顺序和细节会随 Webpack 版本、插件及配置变化。

145. 说说你对webpack的理解?解决了什么问题? ​

答案:

Webpack 是面向现代 JavaScript 应用的静态模块打包器。它从入口出发构建依赖图,把 JS、CSS、图片等资源统一纳入模块体系,再通过 Loader 转换、Plugin 扩展,输出一个或多个可部署资源。它解决了模块兼容、资源依赖管理、代码分割、懒加载、缓存、压缩和开发期 HMR 等问题,适合构建流程复杂的大型应用;库打包则要权衡 Rollup 等工具。

146. Babel的原理是什么 ​

答案:

Babel 的核心是 AST 转换流水线:Parse 阶段用解析器把源码变成抽象语法树;Transform 阶段由插件遍历并修改 AST,例如把箭头函数、可选链转换为目标环境支持的语法,同时可注入 polyfill;Generate 阶段把新 AST 生成代码和 Source Map。Babel 主要转换语法,运行时 API 是否补齐取决于 core-js、preset-env 等配置,不能把两者混为一谈。

147. webpack的热更新是如何做到的?说明其原理 ​

答案:

Webpack HMR 由编译端、开发服务器和浏览器端 runtime 协同完成。文件变化后,Webpack 增量编译并把新模块放入内存,服务器通过 WebSocket 通知客户端新的 hash;客户端请求更新清单和模块代码,HMR runtime 按依赖关系执行 accept 回调替换模块并保留可保留的状态。若没有可接受边界、出现错误或配置不支持,客户端会回退到整页刷新;这与 live reload 不同。

148. 如何提高webpack的打包速度 ​

答案:

提高打包速度应先用 profile 或构建分析定位瓶颈。Webpack 5 优先开启文件系统持久化缓存;限制 Loader 的匹配范围,避免处理 node_modules;用 Babel 的 cacheDirectory,并按需采用 thread-loader;拆出稳定依赖或使用 externals;减少不必要的 polyfill、Source Map 和压缩开销;开发时使用增量编译。并行化有启动成本,小项目不一定更快,必须用实际构建时间验证。

149. webpack的构建流程是什么 ​

答案:

构建流程可概括为:读取配置与命令行参数并初始化 Compiler;加载插件后开始编译;根据 entry 找到入口;递归解析依赖并按 Loader 转换模块,建立依赖图;将模块组合为 Chunk;根据模板和插件生成最终资源,执行压缩、注入等处理;最后按 output 写入文件系统。Loader 主要参与模块转换,Plugin 可在各阶段 Hook 中改变构建结果。

150. webpack的Loader和Plugin的不同 ​

答案:

Loader 解决“如何把某类模块转换成 Webpack 能理解的内容”,配置在 module.rules,可串联处理,例如 sass-loader、css-loader、babel-loader。Plugin 解决“如何扩展构建流程和产物”,通过生命周期 Hook 参与编译、资源生成和输出,配置在 plugins,例如生成 HTML 或抽离 CSS。简单说,Loader 面向单个模块,Plugin 面向整个构建过程;两者都可能影响最终产物。

151. webpack有哪些常见的Plugin ​

答案:

常见 Plugin 有:HtmlWebpackPlugin 自动生成并注入入口资源的 HTML;DefinePlugin 注入构建期常量;MiniCssExtractPlugin 抽离 CSS 文件;CopyWebpackPlugin 复制无需编译的静态资源;webpack-bundle-analyzer 分析各模块体积;TerserPlugin 压缩 JavaScript。Webpack 5 还可直接用资源模块替代部分旧 Loader,插件名称和默认配置应以项目版本为准。

152. webpack、rollup、parcel优劣 ​

答案:

Webpack 功能和生态最完整,适合依赖复杂、需要代码分割、兼容处理和深度定制的应用,但配置与构建成本较高。Rollup 以 ESM、Tree Shaking 和较干净的输出见长,适合 JavaScript/TypeScript 库,不过应用级资源处理和运行时能力通常依赖插件。Parcel 强调零配置和快速上手,适合小型或试验项目,复杂场景的可控性与生态不如前两者。选择应看产物类型和定制需求。

153. Webpack中 loader的作用是什么,以及常用loader有哪些 ​

答案:

Loader 是 Webpack 的模块转换器,让原本只能理解 JavaScript 的构建器处理 TypeScript、Sass、CSS、图片和字体等资源。它们写在 module.rules 中,可按 test 匹配并串联。常用的有 babel-loader、ts-loader、css-loader、style-loader、sass-loader、postcss-loader;Webpack 5 推荐用内置 asset modules 处理资源,替代部分 file-loader 和 url-loader。

154. 谈谈你对 Webpack的认识 ​

答案:

我把 Webpack 理解为以依赖图为核心的前端构建系统:从入口递归分析模块,把 JS、样式和静态资源纳入统一依赖关系,再用 Loader 做转换、Plugin 扩展生命周期,输出可部署的 Bundle 或 Chunk。它解决了模块兼容、资源管理、代码分割、懒加载、Tree Shaking、压缩、缓存和 HMR 等问题。优势是能力和生态全面,代价是配置复杂、构建成本较高,现代项目也应结合 Vite 等方案评估。

基于 VitePress 构建的个人学习文档知识库