1.项目亮点
前端项目亮点的撰写需要结合技术深度、业务价值、用户体验等维度,突出项目的差异化优势和个人贡献。以下是具体思路和示例,帮助你清晰、有说服力地呈现亮点:
一、核心原则:从 “技术实现” 到 “价值落地”
亮点不是简单罗列技术栈,而是要说明:
- 解决了什么问题(业务痛点 / 技术难点);
- 用了什么方案(技术选型 / 创新点);
- 带来了什么价值(性能提升 / 体验优化 / 效率提升等,最好有数据支撑)。
二、常见亮点方向及示例
1. 性能优化(最易量化,推荐优先写)
针对页面加载慢、交互卡顿等问题,通过技术手段提升性能,用数据体现效果。示例:
- 「优化首屏加载速度:通过路由懒加载 + 组件按需引入拆分代码包,配合 Service Worker 缓存静态资源,使首屏加载时间从 3.2s 降至 0.8s,Lighthouse 性能评分从 65 分提升至 92 分,用户停留时长增加 20%。」
- 「解决大数据列表卡顿:采用虚拟滚动(react-window)替代传统列表渲染,当列表数据达 10000 + 时,DOM 节点从 10000 + 减少至 30+,滚动帧率从 20fps 提升至 60fps,操作流畅度显著提升。」
- 「图片优化方案:实现响应式图片加载(srcset+size),结合 WebP 格式自动降级,图片加载体积减少 40%,带宽成本降低 35%。」
2. 技术架构 / 工程化创新
体现对项目架构的思考、工程化工具的定制能力,或对技术栈的深度应用。示例:
- 「搭建组件库与设计系统:基于 TypeScript+Storybook 开发 15 + 通用业务组件,统一设计规范,支持主题切换,新页面开发效率提升 50%,UI 一致性问题减少 80%。」
- 「前端工程化体系搭建:配置 webpack 多环境打包策略,集成 ESLint+Prettier+husky 实现代码规范自动化校验,配合 CI/CD 流程,将部署时间从 30 分钟缩短至 5 分钟,线上 bug 率降低 40%。」
- 「微前端架构落地:采用 qiankun 将 3 个独立项目整合为统一平台,实现子应用独立开发 / 部署,主应用与子应用通信延迟 < 10ms,团队协作效率提升 60%,避免重复开发成本约 20 人天。」
3. 用户体验(UX)优化
从交互细节、异常处理、场景化设计等方面提升用户体验,体现对用户的关注。示例:
- 「表单交互优化:实现表单实时校验 + 错误提示本地化,添加输入防抖与自动填充,结合骨架屏减少等待焦虑,表单提交成功率从 75% 提升至 92%,用户反馈 “填写更顺畅”。」
- 「离线功能支持:针对弱网场景,基于 IndexedDB 实现核心数据本地存储,断网时仍可查看历史数据并缓存操作,网络恢复后自动同步,解决了外勤人员数据录入丢失问题,用户满意度提升 30%。」
- 「无障碍(a11y)适配:优化语义化 HTML 结构,添加 ARIA 属性,支持键盘导航与屏幕阅读器,通过 WCAG 2.1 AA 级认证,覆盖更多用户群体。」
4. 复杂业务问题解决
针对业务中的复杂场景(如多状态交互、实时数据处理),给出优雅的技术解决方案。示例:
- 「实时数据看板实现:基于 WebSocket+RxJS 处理高频数据更新(每秒 30 + 条),通过节流与数据聚合减少 DOM 操作,配合 Canvas 绘制动态图表,实现千万级数据的实时可视化,延迟控制在 100ms 内,满足运营实时监控需求。」
- 「复杂权限系统设计:采用 “RBAC + 数据权限” 双层模型,前端通过动态路由 + 指令权限(v-permission)实现页面 / 按钮级权限控制,支持权限动态刷新,无需刷新页面,适配 10 + 角色的精细化权限管理,权限配置效率提升 80%。」
5. 跨端 / 多端适配
体现对多设备、多平台的适配能力,尤其是响应式、小程序、App 等场景。示例:
- 「全端适配方案:基于 Tailwind CSS + 媒体查询实现响应式布局,结合 Flexbox/Grid 解决多终端(手机 / 平板 / PC)显示问题,兼容 iOS 12+、Android 8 + 及主流浏览器,页面适配问题从平均 5 个 / 版本降至 0.5 个 / 版本。」
- 「跨端复用方案:使用 Taro 框架开发微信 / 支付宝小程序,通过抽象业务逻辑层(hooks)实现 70% 代码复用,双端开发周期从 8 周缩短至 4 周,维护成本降低 60%。」
三、撰写技巧
- 数据化呈现:用具体数字(如 “提升 30%”“减少 50%”)替代模糊描述(如 “大幅提升”),增强可信度。
- 突出个人角色:明确自己在亮点中的贡献(如 “主导设计”“独立实现”“核心开发者”)。
- 结合业务场景:说明技术方案如何服务于业务目标(如 “降低成本”“提升转化率”“支持新业务上线”)。
- 避免技术堆砌:不罗列 “使用 React/Vue” 等基础技术,重点写 “基于 React 如何解决了 XX 问题”。
2.多包管理问题
在大型前端项目中,随着业务复杂度提升,往往会拆分为多个子包(如组件库、工具库、业务模块包等),此时会面临多包管理(Monorepo) 的问题:如何高效管理依赖、版本、构建、发布等流程,避免重复工作和版本混乱。
一、多包管理的核心痛点
- 依赖冗余:多个包重复安装相同依赖,占用磁盘空间,构建效率低。
- 版本不一致:包之间存在依赖关系时,版本同步困难,易出现 “包 A 依赖包 B@1.0,包 C 依赖包 B@2.0” 的冲突。
- 发布繁琐:手动逐个发布包,易遗漏版本更新,且无法保证发布顺序(依赖包需先于使用方发布)。
- 开发体验差:本地开发时,修改一个包后需手动 link 到其他依赖包,流程繁琐。
二、解决方案:使用 Monorepo 工具
通过专门的多包管理工具统一管理所有子包,核心目标是共享依赖、统一版本、简化发布流程。主流工具包括:
1. pnpm workspace(推荐,轻量高效)
pnpm 是新一代包管理器,内置 workspace 功能,适合中小型多包项目,配置简单,性能优异。
核心特性:
- 依赖共享:所有子包共享一个
node_modules,通过硬链接 / 符号链接避免重复安装。 - 统一配置:在根目录
pnpm-workspace.yaml中声明子包路径。
2. lerna(经典工具,适合大型项目)
lerna 是老牌 Monorepo 工具,功能全面,支持版本管理、批量发布等,但依赖 npm/yarn,性能略逊于 pnpm。
3. 其他工具
- nx:强工程化工具,适合大型团队,内置构建缓存、任务调度、代码生成等,学习成本较高。
- turborepo:Vercel 推出的高性能构建工具,专注于任务缓存和并行执行,常与 pnpm 搭配使用。
三、关键问题解决
1. 版本管理与发布
通过 changeset 工具追踪包的变更,自动生成版本号和更新日志:
-
安装:
pnpm add @changesets/cli -w -D -
初始化:
pnpm changeset init -
提交变更:
- 修改子包后,执行
pnpm changeset,选择变更类型(patch/minor/major),填写更新说明。
- 修改子包后,执行
-
发布:
pnpm changeset version:根据变更记录自动更新版本号、生成 CHANGELOG。pnpm changeset publish:发布所有更新的包(按依赖顺序发布,避免依赖错误)。
2. 依赖共享策略
- 公共依赖:在根目录
package.json的dependencies或devDependencies中声明(如typescript、eslint),所有子包共享,避免重复安装。 - 子包专属依赖:在子包自己的
package.json中声明(如业务包依赖的axios)。 - 内部包依赖:通过工具(pnpm/lerna)的链接功能,直接依赖内部包的名称(如
pkg1),无需写相对路径。
3. 构建与任务调度
- 并行执行:工具(如 lerna、turborepo)支持并行执行多个子包的构建任务(如
lerna run build --parallel),提升效率。 - 依赖顺序:若
pkg2依赖pkg1的构建产物,工具会自动先构建pkg1,再构建pkg2(如 lerna 的--sort选项)。 - 构建缓存:nx/turborepo 支持缓存构建结果,未修改的包可复用缓存,大幅减少构建时间。
4. 代码规范与协作
- 根目录统一配置
eslint、prettier、husky等工具,所有子包共享同一套规范。 - 通过
tsconfig.json继承(extends)实现 TypeScript 配置共享,避免重复配置。
四、最佳实践总结
-
工具选择:
- 中小型项目:优先
pnpm workspace + changeset(轻量、高效)。 - 大型复杂项目:
lerna + pnpm或nx(强工程化支持)。
- 中小型项目:优先
得益于pnpm的包管理机制,node_modules目录下只展示了lodash一个依赖包,lodash的相关依赖包完全没展示在其中,全都展示在了.pnpm里面,再通过软链接的形式指向真实的地址,简洁分明,避免了互相依赖的情况。
Changesets 是一个用于 monorepo 项目下版本以及 changelog 文件管理的工具。管理monorepo 项目下子项目版本的更新、changelog 文件生成、包的发布。
3.tree shaking原理,没有引用的文件但是有副作用的是怎么处理?
ree Shaking 是前端打包工具(如 Webpack、Rollup)用于移除代码中未被引用的部分(Dead Code) 的优化技术,核心目标是减小最终打包体积。其工作原理基于 ES6 模块系统(ESM)的静态分析特性:
-
ES6 模块的静态性:ESM 采用
import/export语法,模块依赖关系在编译时(打包阶段) 即可确定,无需运行时解析。例如:javascript
运行
// 明确的导入导出,可静态分析 export const a = 1; export const b = 2; import { a } from './module'; // 仅导入a,b未被引用这种静态性使得打包工具能通过 AST(抽象语法树)分析出哪些导出成员未被任何地方导入,标记为 “未使用代码”。
-
标记未使用代码:打包工具(如 Webpack 配合 Terser)会遍历模块依赖树,标记所有未被引用的导出成员(变量、函数、类等)。
-
移除未使用代码:在代码压缩阶段(如 Terser 处理),被标记的未使用代码会被直接删除,从而实现 “摇树” 效果。
问题:有副作用但未被引用的文件如何处理?
Tree Shaking 的关键前提是:未被引用的代码必须是 “无副作用” 的。如果一个文件 / 模块未被引用,但包含副作用(Side Effect) (即代码执行会影响外部环境,如修改全局变量、注册事件、操作 DOM 等),直接移除可能导致程序出错。
1. 什么是 “副作用”?
副作用指代码执行后对外部环境产生的不可预测影响,例如:
javascript
运行
// module.js(未被任何地方import,但有副作用)
window.globalVar = 'hello'; // 修改全局变量
document.body.addEventListener('click', () => {}); // 注册事件
console.log('这段代码会被执行'); // 控制台输出
即使该模块未被引用,若被打包,这些代码会在运行时执行;若被 Tree Shaking 误删,可能导致依赖全局变量的功能失效。
2. 打包工具如何处理有副作用的未引用文件?
打包工具默认无法自动判断代码是否有副作用,因此需要开发者通过配置明确声明 “哪些文件 / 代码无副作用”,避免误删有副作用的代码。
(1)package.json 中的 sideEffects 配置(核心)
这是告诉打包工具 “哪些文件有副作用” 的标准方式,通常在库或项目的根 package.json 中配置:
-
sideEffects: false:声明所有文件均无副作用,打包工具可安全移除未引用的任何文件 / 导出。json
{ "sideEffects": false } -
sideEffects: ["*.css", "./utils/logger.js"]:声明指定文件有副作用,即使未被引用也不会被移除;其他文件默认无副作用。- 常见场景:CSS 文件(
import './style.css'本身无导出,但执行时会插入样式到 DOM,有副作用)、全局初始化脚本等。
- 常见场景:CSS 文件(
-
不配置
sideEffects:打包工具会默认所有文件可能有副作用,此时即使代码未被引用,也可能被保留(避免误删),导致 Tree Shaking 失效。
(2)代码层面的副作用标记
对于模块内的部分代码(而非整个文件),可通过注释或语法暗示其无副作用,辅助工具识别:
-
纯函数(无副作用):
javascript
运行
// 无副作用:仅依赖输入,不修改外部 export const add = (a, b) => a + b; -
避免在模块顶层写副作用代码:模块顶层代码(不在函数 / 类内部)会在模块加载时自动执行,容易产生副作用。如需初始化,建议通过显式调用函数触发:
javascript
运行
// 不好:顶层副作用代码,未被引用也可能执行 window.init = true; // 好:将副作用封装为函数,需显式调用才执行 export const init = () => { window.init = true; };
3. 总结:未引用但有副作用的文件处理流程
- 开发者通过
package.json的sideEffects配置,明确标记有副作用的文件(如 CSS、全局初始化脚本)。 - 打包工具在分析时,会保留
sideEffects列表中未被引用的文件(因为它们有副作用)。 - 对于未在
sideEffects中声明且未被引用的文件,工具会判断为无副作用,通过 Tree Shaking 移除。
关键结论:Tree Shaking 仅能安全移除 “无副作用且未被引用” 的代码;有副作用的未引用文件需通过 sideEffects 配置手动声明,避免被误删。合理配置 sideEffects 是确保 Tree Shaking 生效且不破坏功能的核心。
4.key的作用,如何复用节点,这个过程
在 Vue 中,key 是用于标识列表中节点的特殊属性,其核心作用是帮助 Vue 的虚拟 DOM(VNode)diff 算法精准识别节点的身份,从而优化渲染性能、避免不必要的节点销毁与重建。
一、key 的核心作用
-
标识节点唯一性当列表数据发生变化(如增删、排序、过滤)时,Vue 会通过
key判断新旧 VNode 是否为同一节点。- 若
key相同:Vue 会认为这是同一个节点,尝试复用该节点(仅更新内容)。 - 若
key不同:Vue 会销毁旧节点,创建新节点。
- 若
-
避免渲染错误若不设置
key或使用不稳定的key(如索引index),可能导致节点复用错误(如表单输入值错乱、组件状态异常)。
二、节点复用的过程(结合 diff 算法)
Vue 的 diff 算法会对新旧列表进行对比,key 是对比的核心依据,具体流程如下:
1. 无 key 时的默认行为(不推荐)
若列表项未设置 key,Vue 会按位置索引复用节点(即默认用 index 作为隐式 key)。
-
示例:
html
预览
<!-- 原始列表 --> <li>苹果</li> <li>香蕉</li> <li>橙子</li> <!-- 插入新项后 --> <li>苹果</li> <li>西瓜</li> <!-- 新项插入到第2位 --> <li>香蕉</li> <li>橙子</li>- 无
key时,Vue 会认为:旧第 1 个节点 → 新第 1 个节点(复用,内容不变);旧第 2 个节点 → 新第 2 个节点(复用,但内容从 “香蕉” 改为 “西瓜”);旧第 3 个节点 → 新第 3 个节点(复用,内容从 “橙子” 改为 “香蕉”);新增第 4 个节点(创建 “橙子”)。 - 问题:明明只是插入了 “西瓜”,却导致后续所有节点都被更新(而非复用),性能浪费。
- 无
2. 有 key 时的精准复用(推荐)
当设置唯一且稳定的 key(如数据的 id),Vue 会通过 key 匹配新旧节点,实现最小化更新:
-
示例:
html
预览
<!-- 原始列表(key 为数据 id) --> <li :key="item.id">苹果(id: 1)</li> <li :key="item.id">香蕉(id: 2)</li> <li :key="item.id">橙子(id: 3)</li> <!-- 插入新项后 --> <li :key="item.id">苹果(id: 1)</li> <li :key="item.id">西瓜(id: 4)</li> <!-- 新项 id:4 --> <li :key="item.id">香蕉(id: 2)</li> <li :key="item.id">橙子(id: 3)</li>- 有
key时,Vue 会:匹配key=1→ 复用节点(内容不变);发现key=4是新节点 → 创建并插入;匹配key=2→ 复用节点(内容不变);匹配key=3→ 复用节点(内容不变)。 - 结果:仅新增 “西瓜” 节点,其他节点完全复用,性能最优。
- 有
三、如何正确设置 key 以优化复用?
-
使用唯一且稳定的标识优先用数据自身的唯一 ID(如后端返回的
id),确保key在列表变化时(增删、排序)仍能准确对应节点。html
预览
<li v-for="item in list" :key="item.id">{{ item.name }}</li> -
**避免用索引
index作为key**当列表发生排序、删除等操作时,index会变化(如删除第 1 项,后续项的index都会减 1),导致key不稳定,Vue 会误判节点身份,反而破坏复用。 -
特殊场景:强制重建节点若需主动销毁旧节点、创建新节点(如重置组件状态),可使用动态
key(如时间戳):html
预览
<my-component :key="refreshKey"></my-component> <button @click="refreshKey = Date.now()">重置组件</button>点击按钮时,
key变化,Vue 会销毁旧组件实例,创建新实例,达到 “重置” 效果。
总结
key的核心是给节点一个唯一身份标识,帮助 Vue 区分不同节点。- 有
key时,Vue 通过key匹配新旧节点,只更新变化的部分,复用未变化的节点,大幅提升列表渲染性能。 - 正确用法:用数据唯一 ID 作为
key;避免用index;必要时用动态key强制重建节点。
5.有实现过打包插件吗,输入输出是什么
打包插件的本质是:
- 输入:构建上下文 + 配置参数 + 文件内容
- 输出:修改构建过程 + 生成/转换文件 + 自定义行为
- 核心:通过 Webpack 钩子介入编译流程,对资源进行自定义处理,扩展打包能力。
实现插件需要深入理解目标构建工具的生命周期和API,在合适的时机介入并输出期望的结果。这种能力对于前端工程化建设和性能优化至关重要。
6.脚手架如何实现,如何区分模板
脚手架(Scaffold)是用于快速生成项目基础结构的工具(如 Vue CLI、Create React App),核心目标是标准化项目初始化流程,减少重复配置工作。其实现主要分为「脚手架核心逻辑」和「模板管理」两部分,以下是具体实现思路:
一、脚手架的核心实现流程
脚手架的核心是「接收用户输入 → 选择模板 → 生成项目」,通常基于 Node.js 开发,依赖命令行交互、文件操作等能力。
二、模板的区分与管理
模板是脚手架的 “骨架”,需要根据不同场景(如框架类型、技术栈)区分管理,常见方式有以下两种:
1. 本地模板(适合简单场景)
将模板文件直接放在脚手架的 templates 目录下,按类型分类(如 vue3-ts、react-js),通过文件夹名称区分模板。
- 变量替换:模板文件中需动态替换的内容(如项目名称)用
ejs语法标记(<%= name %>),生成时通过ejs.renderFile注入实际值。 - 区分逻辑:用户选择模板名称后,直接读取对应文件夹的内容,简单直观。
2. 远程模板(适合复杂场景 / 团队协作)
将模板托管在 Git 仓库(如 GitHub、GitLab),通过仓库地址或标识区分模板(如 vue3-ts 对应 https://github.com/xxx/vue3-ts-template),脚手架动态下载。
实现步骤:
- 维护模板列表:在脚手架中内置模板映射表,或通过接口动态获取:
- 下载远程模板:使用
download-git-repo克隆仓库到本地临时目录,再复制到目标项目: - 区分逻辑:通过模板名称映射到不同仓库地址,支持模板独立维护和版本更新(无需升级脚手架)。
三、高级功能:模板定制与筛选
为了更灵活地区分模板,可增加以下功能:
- 模板分类:按 “框架”“端类型”“业务场景” 分类(如
pc/vue3、mobile/react),用户通过多级选择筛选。 - 模板版本:支持指定模板版本(如
vue3-ts@2.0),在仓库中通过 tag 或分支区分。 - 自定义模板:允许用户输入自己的模板仓库地址(
my-cli create my-proj --template gitlab:my/template)。
总结
-
脚手架实现核心:命令行解析 → 用户交互 → 模板渲染 → 项目生成,依赖
commander(命令)、inquirer(交互)、fs-extra(文件)、ejs(模板引擎)等工具。 -
模板区分方式:
- 本地模板:通过文件夹名称区分,适合简单、固定的模板;
- 远程模板:通过仓库地址映射区分,适合复杂、需要独立维护的模板。
-
核心目标是让用户通过简单选择,快速获得符合规范的项目结构,减少重复配置成本。
7.webpack用了哪些优化组件
Webpack 作为前端构建工具,提供了丰富的优化手段,涵盖打包体积减小、构建速度提升、运行时性能优化等维度。这些优化通过内置功能、插件(Plugin)或加载器(Loader)实现,以下是核心优化组件及对应的优化方向:
Webpack 的优化组件可分为三类:
- 体积优化:Tree Shaking、代码分割、Terser/CSS 压缩、资源优化;
- 速度优化:缓存(
cache、babel-loader)、多进程(thread-loader)、缩小处理范围; - 运行时优化:
runtimeChunk、contenthash、模块联邦。 - 内置功能:通过
splitChunks拆分公共代码、第三方库,避免重复打包。
8.CND原理
核心思想:就近访问
CDN 的核心原理非常简单:将内容缓存在距离用户更近的服务器上,让用户不用千里迢迢去访问源站,而是从附近的缓存节点获取资源。 CDN(Content Delivery Network,内容分发网络)是一种通过分布式节点网络加速内容传输的技术,核心目标是让用户从离自己最近的节点获取资源,减少延迟并提高访问速度。
一、CDN 的核心组成
- 源站(Origin Server) :存储原始内容的服务器(如网站的主服务器)。
- CDN 节点(Edge Node) :分布在全球各地的缓存服务器(边缘节点),直接向用户提供内容。
- DNS 负载均衡系统:负责将用户请求导向 “最优节点”(通常是距离最近、负载最低的节点)。
- 缓存系统:节点服务器对资源的缓存管理(如哪些内容缓存、缓存时长)。
二、CDN 工作流程(以 “用户访问一张图片” 为例)
1. 用户发起请求
用户通过浏览器访问资源 URL(如 https://cdn.example.com/image.jpg)。
2. DNS 解析与节点选择
- 本地 DNS 服务器接收请求,发现域名后缀为 CDN 服务商(如
cdn.example.com),将请求转发至 CDN 的 DNS 负载均衡系统。 - 负载均衡系统根据用户 IP 地址判断其地理位置(如 “北京”),并查询该区域内的 CDN 节点状态(负载、健康度),选择最优节点(如 “北京机房的节点 A”)。
- 负载均衡系统将节点 A 的 IP 地址返回给用户浏览器。
3. 节点缓存检查
用户浏览器向节点 A 发起资源请求(image.jpg):
-
情况 1:节点已缓存该资源(且未过期):节点 A 直接将缓存的
image.jpg返回给用户,流程结束(耗时极短,通常 10-50ms)。 -
情况 2:节点未缓存或资源已过期:节点 A 会向源站发起请求,获取
image.jpg后:- 先将资源缓存到本地(按源站设定的缓存规则,如有效期 1 天);
- 再将资源返回给用户。
4. 缓存更新与同步
- 当源站内容更新(如
image.jpg被替换),可通过 URL 重命名(如加哈希image_v2.jpg)或主动调用 CDN 接口刷新缓存,确保节点同步最新内容。 - 部分 CDN 支持 “预热” 功能:提前将热门资源(如活动页面图片)推送至各节点缓存,避免用户首次访问时节点回源站拉取的延迟。
三、CDN 的核心技术原理
-
缓存机制
- 基于 HTTP 缓存头(如
Cache-Control、Expires)控制资源缓存时长,减少回源站次数。 - 对静态资源(图片、JS、CSS、视频)缓存效果最佳(内容稳定,更新频率低);动态资源(如 PHP 生成的页面)通常不缓存或短时间缓存。
- 基于 HTTP 缓存头(如
-
智能路由与负载均衡
- 通过 IP 地理位置库(如 MaxMind)定位用户区域,优先选择同区域节点。
- 实时监控节点负载(CPU、带宽、连接数),避免将请求导向过载节点。
-
边缘计算(部分高级 CDN)
- 在节点服务器上运行简单逻辑(如图片裁剪、动态内容拼接),减少源站压力,进一步降低延迟。
四、CDN 的核心价值
- 提速:用户从近节点获取资源,减少跨地域传输延迟(如北京用户访问美国源站的资源,通过北京 CDN 节点可将延迟从 300ms 降至 30ms)。
- 降负载:源站只需向 CDN 节点传输一次资源,后续用户请求由节点承担,大幅减少源站带宽和服务器压力。
- 高可用:某节点故障时,负载均衡系统会自动将请求切换到其他健康节点,避免服务中断。
总结
CDN 的本质是 “分布式缓存 + 智能调度”:通过在全球部署边缘节点,让用户就近获取资源;通过 DNS 负载均衡选择最优节点,结合缓存机制减少回源,最终实现 “更快、更稳、更省” 的内容传输。
9.html和css如何做成渲染树
HTML 和 CSS 生成渲染树(Render Tree)是浏览器渲染页面的核心步骤,目的是将结构化的 HTML 内容与 CSS 样式结合,形成可用于计算布局(Layout)和绘制(Paint)的视觉节点树。整个过程分为 HTML 解析生成 DOM 树、CSS 解析生成 CSSOM 树、DOM 与 CSSOM 结合生成渲染树 三个阶段,具体流程如下:
一、阶段 1:HTML 解析 → DOM 树(Document Object Model)
浏览器加载 HTML 文档后,会按顺序解析标签,将 HTML 结构转换为 DOM 树(一种以节点为单位的树形结构,描述文档的层次关系)。
解析过程:
- 词法分析:将 HTML 字符串拆分为有意义的标记(如
<div>、class="box"等)。 - 语法分析:根据 HTML 语法规则,将标记转换为节点(Node),并建立节点间的父子 / 兄弟关系。
- 生成 DOM 树:根节点为
<html>,子节点为<head>和<body>,以此类推,最终形成完整的树形结构。
二、阶段 2:CSS 解析 → CSSOM 树(CSS Object Model)
同时,浏览器会解析页面中的 CSS(包括 <style> 标签、link 引入的外部 CSS、元素的 style 属性),生成 CSSOM 树(描述所有选择器对应的样式规则,以及规则的层级关系)。
解析过程:
- 词法分析:将 CSS 字符串拆分为标记(如
div、color、#f00等)。 - 语法分析:根据 CSS 语法,将标记转换为样式规则(如
div { color: red; }),并处理优先级(如!important、 specificity 权重)。 - 生成 CSSOM 树:以选择器为索引,组织样式规则,支持快速查找某个 DOM 节点对应的样式。
三、阶段 3:DOM + CSSOM → 渲染树(Render Tree)
渲染树是 DOM 树与 CSSOM 树的结合产物,仅包含需要显示在页面上的节点(排除隐藏节点),并为每个节点附加最终计算后的样式(即 “计算样式”)。
构建过程:
-
遍历 DOM 树:从根节点开始,逐个处理每个可见节点(隐藏节点如
display: none会被跳过)。 -
匹配 CSSOM 样式:为每个 DOM 节点查找 CSSOM 中对应的样式规则(根据选择器匹配,如类名、标签名、ID 等),并计算最终样式(解决样式冲突,遵循优先级规则)。
-
生成渲染树节点:每个渲染树节点包含两部分信息:
- 对应的 DOM 节点结构(如元素类型、层级关系);
- 计算后的样式(如
width、color、font-size等)。
关键特性:
- 排除隐藏节点:
display: none的节点不会进入渲染树(visibility: hidden会进入,因为其仍占据布局空间)。 - 继承样式:子节点会继承父节点的可继承样式(如
font-size、color),无需重复匹配。 - 样式合并:多个样式规则匹配同一节点时,按优先级(specificity)合并为最终计算样式(如
id选择器优先级高于类选择器)。
总结:渲染树的作用
渲染树是浏览器进行 布局(Layout) 和 绘制(Paint) 的基础:
- 布局(回流 / 重排) :根据渲染树计算每个节点的位置和尺寸(如
width、height、left)。 - 绘制(重绘) :根据计算后的布局和样式,将节点绘制到屏幕上(如填充颜色、绘制边框、渲染文本)。
因此,渲染树的构建效率直接影响页面的加载速度和交互性能(如频繁修改 DOM 或样式可能导致渲染树重建,引发布局 / 绘制开销)。
10.为啥用ts
使用 TypeScript(TS)的核心原因是它为 JavaScript(JS)添加了静态类型系统,解决了 JS 作为动态类型语言带来的诸多问题,尤其在中大型项目或团队协作中能显著提升代码质量、开发效率和可维护性。具体优势如下:
1. 提前发现错误,减少运行时 Bug
JS 是动态类型语言,变量类型在运行时才能确定,很多类型错误(如传参类型错误、访问不存在的属性)只能在代码运行时暴露,可能导致线上故障。TS 通过静态类型检查,在编码阶段就能发现这些错误:
2. 增强代码可读性与可维护性
- 类型即文档:变量、函数的类型定义清晰描述了其用途,开发者无需通读实现逻辑就能理解如何使用。
- 重构更安全:修改变量名、函数参数时,TS 会自动检查所有引用位置,确保重构不遗漏,尤其适合大型项目。
3. 提升开发效率,降低协作成本
- 智能提示:IDE(如 VS Code)能基于类型信息提供精准的代码补全、属性提示、函数参数提示,减少查文档的时间。
- 团队协作更顺畅:类型定义是团队成员之间的 “契约”,避免因参数含义、数据结构理解不一致导致的沟通成本和错误。例如,后端接口返回的数据结构可通过 TS 接口定义,前后端开发基于同一类型协作,减少联调问题。
4. 更好地支持复杂业务场景
- 面向对象编程友好:TS 原生支持类(
class)、接口(interface)、继承、泛型等特性,能更优雅地实现复杂业务逻辑(如设计模式、状态管理)。 - 类型细化与控制:通过联合类型(
|)、交叉类型(&)、类型守卫等特性,精确描述复杂数据结构,避免 “一刀切” 的any类型。
5. 兼容 JavaScript,迁移成本低
- TS 是 JS 的超集,所有 JS 代码都可以直接在 TS 中运行,无需一次性重构整个项目。
- 可以逐步为 JS 文件添加类型注释(
.d.ts声明文件),平滑过渡到 TS 开发模式。
6. 丰富的生态支持
- 主流库 / 框架(React、Vue、Node.js 等)都提供完善的 TS 类型定义,开箱即用。
- 构建工具(Webpack、Vite)、测试工具(Jest)等均对 TS 有良好支持,开发流程无缝衔接。
总结:哪些场景尤其适合用 TS?
- 中大型项目:代码量庞大、模块众多时,类型系统能显著降低维护成本。
- 团队协作:多人开发时,类型定义减少沟通成本,避免 “一人写,多人猜”。
- 长期维护的项目:随着时间推移,类型信息能帮助新接手的开发者快速理解代码。
对于小型项目或短期脚本,TS 的类型检查可能显得多余;但对于需要长期迭代、多人协作的业务项目,TS 带来的收益远大于额外的类型定义成本。
11.vue用了哪些设计模式
1. 观察者模式(Observer Pattern)
核心思想:定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象会自动收到通知并更新。Vue 中的应用:
-
响应式系统核心:Vue 2 中通过
Object.defineProperty(Vue 3 中通过Proxy)实现数据劫持,将数据对象转为 “被观察者(Observer)”; -
组件的
data、props等数据变化时,依赖这些数据的视图(Watcher)和计算属性会自动更新。 -
具体流程:
- 数据初始化时,通过
Observer递归劫持所有属性的getter/setter; - 视图渲染或计算属性执行时,触发
getter,收集依赖(Watcher实例); - 数据修改时,触发
setter,通知所有依赖的Watcher执行更新(重新渲染或计算)。
- 数据初始化时,通过
2. 发布 - 订阅模式(Pub/Sub Pattern)
核心思想:通过一个 “事件中心”(中介)连接发布者和订阅者,发布者发送事件,订阅者通过事件中心接收通知,两者无需直接关联。Vue 中的应用:
- 事件总线(Event Bus) :Vue 实例可通过
$on(订阅)、$emit(发布)、$off(取消订阅)实现跨组件通信,本质是发布 - 订阅模式的应用。 - 生命周期钩子:组件的生命周期(如
created、mounted)本质是订阅者模式 ——Vue 内部在特定阶段 “发布” 生命周期事件,开发者通过注册钩子函数 “订阅” 这些事件。
3. 工厂模式(Factory Pattern)
核心思想:通过工厂函数统一创建对象,隐藏对象创建的细节,简化实例化过程。Vue 中的应用:
-
虚拟 DOM 节点创建:
createElement函数(Vue 2 中)或h函数(Vue 3 中)是工厂函数,接收标签名、属性、子节点等参数,返回标准化的 VNode 对象(虚拟 DOM 节点)。javascript
运行
// 工厂函数创建 VNode const vnode = h('div', { class: 'box' }, [h('span', 'hello')]); -
组件实例化:Vue 内部通过工厂逻辑统一处理组件的初始化(如解析选项、初始化生命周期、响应式数据等),开发者只需通过
Vue.component或defineComponent定义组件,无需关心实例化细节。
4. 单例模式(Singleton Pattern)
核心思想:保证一个类仅有一个实例,并提供全局访问点。Vue 中的应用:
- Vue 实例(根实例) :在一个 Vue 应用中,通常只会创建一个根实例(
new Vue(...)),作为整个应用的入口,管理全局状态和路由等,符合单例模式。 - Vuex/Pinia 状态管理:Vuex 的
store或 Pinia 的createPinia()本质是单例,整个应用共享一个状态容器,确保状态的唯一性和全局可访问。
5. 装饰器模式(Decorator Pattern)
核心思想:在不修改原有对象的前提下,通过包装(装饰)为对象添加新功能。Vue 中的应用:
- Vue 3 中的装饰器 API:如
@Component(类组件装饰器)、@Prop(属性装饰器)等(需配合vue-class-component),通过装饰器为组件类添加属性、方法或生命周期钩子。 - 指令系统:自定义指令(如
v-focus、v-tooltip)本质是对 DOM 元素的装饰,在不修改元素本身的情况下,添加额外行为(如自动聚焦、悬浮提示)。
6. 策略模式(Strategy Pattern)
核心思想:定义一系列算法,将每个算法封装起来,使它们可相互替换,根据场景动态选择不同策略。Vue 中的应用:
- 模板编译策略:Vue 对不同平台(Web、Weex、SSR)提供了不同的模板编译策略,编译时根据平台选择对应的渲染函数生成逻辑,而核心编译流程保持一致。
- 过渡动画策略:
transition组件支持多种过渡策略(CSS 过渡、CSS 动画、JS 钩子动画),开发者可通过配置(如name、type)选择不同的动画策略,内部根据配置执行对应的逻辑。
7. 代理模式(Proxy Pattern)
核心思想:通过一个代理对象控制对原对象的访问,可用于拦截、增强或保护原对象的操作。Vue 中的应用:
- Vue 3 响应式系统:使用
Proxy代理数据对象,拦截数据的读取(get)、修改(set)、删除(deleteProperty)等操作,实现依赖收集和触发更新,相比 Vue 2 的Object.defineProperty更强大(支持数组索引、新增属性等)。 - 组件的
$props代理:组件实例通过代理访问props数据(如this.propName实际访问的是this.$props.propName),简化了属性的访问方式。
8. 组合模式(Composite Pattern)
核心思想:将对象组合成树形结构,使单个对象和组合对象(容器)具有一致的接口,便于统一处理。Vue 中的应用:
- 组件树结构:Vue 应用是一个组件树(根组件包含子组件,子组件又包含孙组件),无论是单个组件还是组件树,都通过统一的接口(如
$mount、$destroy、$emit)进行操作,符合组合模式。 - 虚拟 DOM 树:VNode 节点可以是单个元素(如
<div>)或包含子节点的组合节点,通过统一的树形结构描述,渲染时递归处理所有节点,无需区分单个节点和组合节点。
总结
Vue 巧妙地将多种设计模式结合,其中:
- 观察者模式是响应式系统的核心;
- 发布 - 订阅模式支撑了事件通信和生命周期;
- 工厂模式简化了 VNode 和组件的创建;
- 其他模式(单例、装饰器、策略等)则在状态管理、跨平台适配、功能扩展等方面发挥作用。
这些模式的应用使 Vue 具备了灵活性、可扩展性和易用性,同时保证了框架内部逻辑的清晰和解耦。
12.JS事件循环,宏任务,微任务
一、执行栈(Call Stack)
执行栈是 JS 引擎用于管理函数调用的后进先出(LIFO)栈结构,负责同步代码的执行。
-
工作原理:
- 执行全局代码时,创建全局执行上下文(Global Execution Context)并压入栈底。
- 每当调用一个函数,就创建该函数的执行上下文并压入栈顶。
- 函数执行完毕后,其执行上下文从栈顶弹出,继续执行栈中下一个上下文。
- 所有代码执行完毕,栈为空。
-
特点:同步代码阻塞执行栈,只有栈顶的上下文能被执行(“单线程” 特性)。
二、宏任务(Macrotask)与微任务(Microtask)
JS 中异步任务分为宏任务和微任务,它们被分别放入不同的任务队列,等待执行栈清空后按规则执行。
1. 宏任务(Macrotask)
定义:优先级较低的异步任务,执行完后会触发 UI 渲染(如果需要),并检查是否有微任务。常见类型:
setTimeout、setInterval(定时器)setImmediate(Node 环境)I/O操作(如文件读取、网络请求)UI 渲染(浏览器环境)- 全局代码(
<script>标签内的顶层代码)
2. 微任务(Microtask)
定义:优先级较高的异步任务,总是在当前宏任务执行完毕后、下一个宏任务开始前执行。常见类型:
Promise.then()、Promise.catch()、Promise.finally()async/await(本质是 Promise 的语法糖,await后的代码属于微任务)queueMicrotask()(显式添加微任务)process.nextTick()(Node 环境,优先级高于其他微任务)
三、执行顺序规则(核心)
- 同步代码优先:执行栈先执行所有同步代码,直到栈为空。
- 清空微任务队列:执行栈清空后,立即执行所有微任务(按入队顺序),直到微任务队列为空。
- 执行一个宏任务:微任务执行完后,从宏任务队列中取出一个宏任务执行(执行其同步代码)。
- 重复步骤 2-3:每执行完一个宏任务,就清空一次微任务队列,再执行下一个宏任务,形成循环(称为 “事件循环 Event Loop”)。
最终输出顺序:
1 6 3 5 2 4
五、关键区别与注意点
- 优先级:微任务 > 宏任务(同一轮事件循环中,微任务先于宏任务执行)。
- 数量:每轮事件循环执行一个宏任务,但会执行所有微任务。
async/await细节:await后面的代码会被包装成微任务,例如:
事件循环Event Loop执行机制
-
1.进入到script标签,就进入到了第一次事件循环.
-
2.遇到同步代码,立即执行
-
3.遇到宏任务,放入到宏任务队列里.
-
4.遇到微任务,放入到微任务队列里.
-
5.执行完所有同步代码
-
6.执行微任务代码
-
7.微任务代码执行完毕,本次队列清空
-
寻找下一个宏任务,重复步骤1
- 以此反复直到清空所以宏任务,这种不断重复的执行机制,就叫做事件循环
总结
-
所有同步任务都在主线程上执行,形成一个执行栈(call stack)。
-
遇到异步任务, 进入异步处理模块并注册回调函数; 等到指定的事件完成(如ajax请求响应返回, setTimeout延迟到指定时间)时,异步处理模块会将这个回调函数移入异步任务队列。
-
当栈中的代码执行完毕,执行栈中的任务为空时,主线程会先检查微任务队列中是否有任务,如果有,就将微任务队列中的所有任务依次执行,直到微任务队列为空; 之后再检查宏任务队列中是否有任务,如果有,则取出第一个宏任务加入到执行栈中,之后再清空执行栈,检查微任务,以此循环,直到全部的任务都