数广

24 阅读33分钟

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%。」

三、撰写技巧

  1. 数据化呈现:用具体数字(如 “提升 30%”“减少 50%”)替代模糊描述(如 “大幅提升”),增强可信度。
  2. 突出个人角色:明确自己在亮点中的贡献(如 “主导设计”“独立实现”“核心开发者”)。
  3. 结合业务场景:说明技术方案如何服务于业务目标(如 “降低成本”“提升转化率”“支持新业务上线”)。
  4. 避免技术堆砌:不罗列 “使用 React/Vue” 等基础技术,重点写 “基于 React 如何解决了 XX 问题”。

2.多包管理问题

在大型前端项目中,随着业务复杂度提升,往往会拆分为多个子包(如组件库、工具库、业务模块包等),此时会面临多包管理(Monorepo)  的问题:如何高效管理依赖、版本、构建、发布等流程,避免重复工作和版本混乱。

一、多包管理的核心痛点

  1. 依赖冗余:多个包重复安装相同依赖,占用磁盘空间,构建效率低。
  2. 版本不一致:包之间存在依赖关系时,版本同步困难,易出现 “包 A 依赖包 B@1.0,包 C 依赖包 B@2.0” 的冲突。
  3. 发布繁琐:手动逐个发布包,易遗漏版本更新,且无法保证发布顺序(依赖包需先于使用方发布)。
  4. 开发体验差:本地开发时,修改一个包后需手动 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 工具追踪包的变更,自动生成版本号和更新日志:

  1. 安装:pnpm add @changesets/cli -w -D

  2. 初始化:pnpm changeset init

  3. 提交变更:

    • 修改子包后,执行 pnpm changeset,选择变更类型(patch/minor/major),填写更新说明。
  4. 发布:

    • pnpm changeset version:根据变更记录自动更新版本号、生成 CHANGELOG。
    • pnpm changeset publish:发布所有更新的包(按依赖顺序发布,避免依赖错误)。
2. 依赖共享策略
  • 公共依赖:在根目录 package.json 的 dependencies 或 devDependencies 中声明(如 typescripteslint),所有子包共享,避免重复安装。
  • 子包专属依赖:在子包自己的 package.json 中声明(如业务包依赖的 axios)。
  • 内部包依赖:通过工具(pnpm/lerna)的链接功能,直接依赖内部包的名称(如 pkg1),无需写相对路径。
3. 构建与任务调度
  • 并行执行:工具(如 lerna、turborepo)支持并行执行多个子包的构建任务(如 lerna run build --parallel),提升效率。
  • 依赖顺序:若 pkg2 依赖 pkg1 的构建产物,工具会自动先构建 pkg1,再构建 pkg2(如 lerna 的 --sort 选项)。
  • 构建缓存:nx/turborepo 支持缓存构建结果,未修改的包可复用缓存,大幅减少构建时间。
4. 代码规范与协作
  • 根目录统一配置 eslintprettierhusky 等工具,所有子包共享同一套规范。
  • 通过 tsconfig.json 继承(extends)实现 TypeScript 配置共享,避免重复配置。

四、最佳实践总结

  1. 工具选择

    • 中小型项目:优先 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)的静态分析特性

  1. ES6 模块的静态性:ESM 采用 import/export 语法,模块依赖关系在编译时(打包阶段)  即可确定,无需运行时解析。例如:

    javascript

    运行

    // 明确的导入导出,可静态分析
    export const a = 1;
    export const b = 2;
    import { a } from './module'; // 仅导入a,b未被引用
    

    这种静态性使得打包工具能通过 AST(抽象语法树)分析出哪些导出成员未被任何地方导入,标记为 “未使用代码”。

  2. 标记未使用代码:打包工具(如 Webpack 配合 Terser)会遍历模块依赖树,标记所有未被引用的导出成员(变量、函数、类等)。

  3. 移除未使用代码:在代码压缩阶段(如 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,有副作用)、全局初始化脚本等。
  • 不配置 sideEffects:打包工具会默认所有文件可能有副作用,此时即使代码未被引用,也可能被保留(避免误删),导致 Tree Shaking 失效。

(2)代码层面的副作用标记

对于模块内的部分代码(而非整个文件),可通过注释或语法暗示其无副作用,辅助工具识别:

  • 纯函数(无副作用):

    javascript

    运行

    // 无副作用:仅依赖输入,不修改外部
    export const add = (a, b) => a + b;
    
  • 避免在模块顶层写副作用代码:模块顶层代码(不在函数 / 类内部)会在模块加载时自动执行,容易产生副作用。如需初始化,建议通过显式调用函数触发:

    javascript

    运行

    // 不好:顶层副作用代码,未被引用也可能执行
    window.init = true;
    
    // 好:将副作用封装为函数,需显式调用才执行
    export const init = () => { window.init = true; };
    
3. 总结:未引用但有副作用的文件处理流程
  1. 开发者通过 package.json 的 sideEffects 配置,明确标记有副作用的文件(如 CSS、全局初始化脚本)。
  2. 打包工具在分析时,会保留 sideEffects 列表中未被引用的文件(因为它们有副作用)。
  3. 对于未在 sideEffects 中声明且未被引用的文件,工具会判断为无副作用,通过 Tree Shaking 移除。

关键结论:Tree Shaking 仅能安全移除 “无副作用且未被引用” 的代码;有副作用的未引用文件需通过 sideEffects 配置手动声明,避免被误删。合理配置 sideEffects 是确保 Tree Shaking 生效且不破坏功能的核心。

4.key的作用,如何复用节点,这个过程

在 Vue 中,key 是用于标识列表中节点的特殊属性,其核心作用是帮助 Vue 的虚拟 DOM(VNode)diff 算法精准识别节点的身份,从而优化渲染性能、避免不必要的节点销毁与重建。

一、key 的核心作用

  1. 标识节点唯一性当列表数据发生变化(如增删、排序、过滤)时,Vue 会通过 key 判断新旧 VNode 是否为同一节点。

    • 若 key 相同:Vue 会认为这是同一个节点,尝试复用该节点(仅更新内容)。
    • 若 key 不同:Vue 会销毁旧节点,创建新节点。
  2. 避免渲染错误若不设置 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 以优化复用?

  1. 使用唯一且稳定的标识优先用数据自身的唯一 ID(如后端返回的 id),确保 key 在列表变化时(增删、排序)仍能准确对应节点。

    html

    预览

    <li v-for="item in list" :key="item.id">{{ item.name }}</li>
    
  2. **避免用索引 index 作为 key**当列表发生排序、删除等操作时,index 会变化(如删除第 1 项,后续项的 index 都会减 1),导致 key 不稳定,Vue 会误判节点身份,反而破坏复用。

  3. 特殊场景:强制重建节点若需主动销毁旧节点、创建新节点(如重置组件状态),可使用动态 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.有实现过打包插件吗,输入输出是什么

image.png 打包插件的本质是:

  • 输入:构建上下文 + 配置参数 + 文件内容
  • 输出:修改构建过程 + 生成/转换文件 + 自定义行为
  • 核心:通过 Webpack 钩子介入编译流程,对资源进行自定义处理,扩展打包能力。

实现插件需要深入理解目标构建工具的生命周期和API,在合适的时机介入并输出期望的结果。这种能力对于前端工程化建设和性能优化至关重要。

6.脚手架如何实现,如何区分模板

脚手架(Scaffold)是用于快速生成项目基础结构的工具(如 Vue CLI、Create React App),核心目标是标准化项目初始化流程,减少重复配置工作。其实现主要分为「脚手架核心逻辑」和「模板管理」两部分,以下是具体实现思路:

一、脚手架的核心实现流程

脚手架的核心是「接收用户输入 → 选择模板 → 生成项目」,通常基于 Node.js 开发,依赖命令行交互、文件操作等能力。

二、模板的区分与管理

模板是脚手架的 “骨架”,需要根据不同场景(如框架类型、技术栈)区分管理,常见方式有以下两种:

1. 本地模板(适合简单场景)

将模板文件直接放在脚手架的 templates 目录下,按类型分类(如 vue3-tsreact-js),通过文件夹名称区分模板。

  • 变量替换:模板文件中需动态替换的内容(如项目名称)用 ejs 语法标记(<%= name %>),生成时通过 ejs.renderFile 注入实际值。
  • 区分逻辑:用户选择模板名称后,直接读取对应文件夹的内容,简单直观。
2. 远程模板(适合复杂场景 / 团队协作)

将模板托管在 Git 仓库(如 GitHub、GitLab),通过仓库地址或标识区分模板(如 vue3-ts 对应 https://github.com/xxx/vue3-ts-template),脚手架动态下载。

实现步骤

  1. 维护模板列表:在脚手架中内置模板映射表,或通过接口动态获取:
  2. 下载远程模板:使用 download-git-repo 克隆仓库到本地临时目录,再复制到目标项目:
  3. 区分逻辑:通过模板名称映射到不同仓库地址,支持模板独立维护和版本更新(无需升级脚手架)。

三、高级功能:模板定制与筛选

为了更灵活地区分模板,可增加以下功能:

  1. 模板分类:按 “框架”“端类型”“业务场景” 分类(如 pc/vue3mobile/react),用户通过多级选择筛选。
  2. 模板版本:支持指定模板版本(如 vue3-ts@2.0),在仓库中通过 tag 或分支区分。
  3. 自定义模板:允许用户输入自己的模板仓库地址(my-cli create my-proj --template gitlab:my/template)。

总结

  • 脚手架实现核心:命令行解析 → 用户交互 → 模板渲染 → 项目生成,依赖 commander(命令)、inquirer(交互)、fs-extra(文件)、ejs(模板引擎)等工具。

  • 模板区分方式

    • 本地模板:通过文件夹名称区分,适合简单、固定的模板;
    • 远程模板:通过仓库地址映射区分,适合复杂、需要独立维护的模板。
  • 核心目标是让用户通过简单选择,快速获得符合规范的项目结构,减少重复配置成本。

7.webpack用了哪些优化组件

Webpack 作为前端构建工具,提供了丰富的优化手段,涵盖打包体积减小构建速度提升运行时性能优化等维度。这些优化通过内置功能、插件(Plugin)或加载器(Loader)实现,以下是核心优化组件及对应的优化方向:

Webpack 的优化组件可分为三类:

  1. 体积优化:Tree Shaking、代码分割、Terser/CSS 压缩、资源优化;
  2. 速度优化:缓存(cachebabel-loader)、多进程(thread-loader)、缩小处理范围;
  3. 运行时优化runtimeChunkcontenthash、模块联邦。
  4. 内置功能:通过 splitChunks 拆分公共代码、第三方库,避免重复打包。

image.png

image.png

8.CND原理

核心思想:就近访问

CDN 的核心原理非常简单:将内容缓存在距离用户更近的服务器上,让用户不用千里迢迢去访问源站,而是从附近的缓存节点获取资源。 CDN(Content Delivery Network,内容分发网络)是一种通过分布式节点网络加速内容传输的技术,核心目标是让用户从离自己最近的节点获取资源,减少延迟并提高访问速度。

一、CDN 的核心组成

  1. 源站(Origin Server) :存储原始内容的服务器(如网站的主服务器)。
  2. CDN 节点(Edge Node) :分布在全球各地的缓存服务器(边缘节点),直接向用户提供内容。
  3. DNS 负载均衡系统:负责将用户请求导向 “最优节点”(通常是距离最近、负载最低的节点)。
  4. 缓存系统:节点服务器对资源的缓存管理(如哪些内容缓存、缓存时长)。

二、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 的核心技术原理

  1. 缓存机制

    • 基于 HTTP 缓存头(如 Cache-ControlExpires)控制资源缓存时长,减少回源站次数。
    • 对静态资源(图片、JS、CSS、视频)缓存效果最佳(内容稳定,更新频率低);动态资源(如 PHP 生成的页面)通常不缓存或短时间缓存。
  2. 智能路由与负载均衡

    • 通过 IP 地理位置库(如 MaxMind)定位用户区域,优先选择同区域节点。
    • 实时监控节点负载(CPU、带宽、连接数),避免将请求导向过载节点。
  3. 边缘计算(部分高级 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 树(一种以节点为单位的树形结构,描述文档的层次关系)。

解析过程:
  1. 词法分析:将 HTML 字符串拆分为有意义的标记(如 <div>class="box" 等)。
  2. 语法分析:根据 HTML 语法规则,将标记转换为节点(Node),并建立节点间的父子 / 兄弟关系。
  3. 生成 DOM 树:根节点为 <html>,子节点为 <head> 和 <body>,以此类推,最终形成完整的树形结构。

二、阶段 2:CSS 解析 → CSSOM 树(CSS Object Model)

同时,浏览器会解析页面中的 CSS(包括 <style> 标签、link 引入的外部 CSS、元素的 style 属性),生成 CSSOM 树(描述所有选择器对应的样式规则,以及规则的层级关系)。

解析过程:
  1. 词法分析:将 CSS 字符串拆分为标记(如 divcolor#f00 等)。
  2. 语法分析:根据 CSS 语法,将标记转换为样式规则(如 div { color: red; }),并处理优先级(如 !important、 specificity 权重)。
  3. 生成 CSSOM 树:以选择器为索引,组织样式规则,支持快速查找某个 DOM 节点对应的样式。

三、阶段 3:DOM + CSSOM → 渲染树(Render Tree)

渲染树是 DOM 树与 CSSOM 树的结合产物,仅包含需要显示在页面上的节点(排除隐藏节点),并为每个节点附加最终计算后的样式(即 “计算样式”)。

构建过程:
  1. 遍历 DOM 树:从根节点开始,逐个处理每个可见节点(隐藏节点如 display: none 会被跳过)。

  2. 匹配 CSSOM 样式:为每个 DOM 节点查找 CSSOM 中对应的样式规则(根据选择器匹配,如类名、标签名、ID 等),并计算最终样式(解决样式冲突,遵循优先级规则)。

  3. 生成渲染树节点:每个渲染树节点包含两部分信息:

    • 对应的 DOM 节点结构(如元素类型、层级关系);
    • 计算后的样式(如 widthcolorfont-size 等)。
关键特性:
  • 排除隐藏节点display: none 的节点不会进入渲染树(visibility: hidden 会进入,因为其仍占据布局空间)。
  • 继承样式:子节点会继承父节点的可继承样式(如 font-sizecolor),无需重复匹配。
  • 样式合并:多个样式规则匹配同一节点时,按优先级(specificity)合并为最终计算样式(如 id 选择器优先级高于类选择器)。

总结:渲染树的作用

渲染树是浏览器进行 布局(Layout)  和 绘制(Paint)  的基础:

  1. 布局(回流 / 重排) :根据渲染树计算每个节点的位置和尺寸(如 widthheightleft)。
  2. 绘制(重绘) :根据计算后的布局和样式,将节点绘制到屏幕上(如填充颜色、绘制边框、渲染文本)。

因此,渲染树的构建效率直接影响页面的加载速度和交互性能(如频繁修改 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)”;

  • 组件的 dataprops 等数据变化时,依赖这些数据的视图(Watcher)和计算属性会自动更新。

  • 具体流程:

    • 数据初始化时,通过 Observer 递归劫持所有属性的 getter/setter
    • 视图渲染或计算属性执行时,触发 getter,收集依赖(Watcher 实例);
    • 数据修改时,触发 setter,通知所有依赖的 Watcher 执行更新(重新渲染或计算)。

2. 发布 - 订阅模式(Pub/Sub Pattern)

核心思想:通过一个 “事件中心”(中介)连接发布者和订阅者,发布者发送事件,订阅者通过事件中心接收通知,两者无需直接关联。Vue 中的应用

  • 事件总线(Event Bus) :Vue 实例可通过 $on(订阅)、$emit(发布)、$off(取消订阅)实现跨组件通信,本质是发布 - 订阅模式的应用。
  • 生命周期钩子:组件的生命周期(如 createdmounted)本质是订阅者模式 ——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-focusv-tooltip)本质是对 DOM 元素的装饰,在不修改元素本身的情况下,添加额外行为(如自动聚焦、悬浮提示)。

6. 策略模式(Strategy Pattern)

核心思想:定义一系列算法,将每个算法封装起来,使它们可相互替换,根据场景动态选择不同策略。Vue 中的应用

  • 模板编译策略:Vue 对不同平台(Web、Weex、SSR)提供了不同的模板编译策略,编译时根据平台选择对应的渲染函数生成逻辑,而核心编译流程保持一致。
  • 过渡动画策略transition 组件支持多种过渡策略(CSS 过渡、CSS 动画、JS 钩子动画),开发者可通过配置(如 nametype)选择不同的动画策略,内部根据配置执行对应的逻辑。

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 具备了灵活性、可扩展性和易用性,同时保证了框架内部逻辑的清晰和解耦。

image.png

12.JS事件循环,宏任务,微任务

一、执行栈(Call Stack)

执行栈是 JS 引擎用于管理函数调用的后进先出(LIFO)栈结构,负责同步代码的执行。

  • 工作原理

    1. 执行全局代码时,创建全局执行上下文(Global Execution Context)并压入栈底。
    2. 每当调用一个函数,就创建该函数的执行上下文并压入栈顶。
    3. 函数执行完毕后,其执行上下文从栈顶弹出,继续执行栈中下一个上下文。
    4. 所有代码执行完毕,栈为空。
  • 特点:同步代码阻塞执行栈,只有栈顶的上下文能被执行(“单线程” 特性)。

二、宏任务(Macrotask)与微任务(Microtask)

JS 中异步任务分为宏任务微任务,它们被分别放入不同的任务队列,等待执行栈清空后按规则执行。

1. 宏任务(Macrotask)

定义:优先级较低的异步任务,执行完后会触发 UI 渲染(如果需要),并检查是否有微任务。常见类型

  • setTimeoutsetInterval(定时器)
  • setImmediate(Node 环境)
  • I/O 操作(如文件读取、网络请求)
  • UI 渲染(浏览器环境)
  • 全局代码(<script> 标签内的顶层代码)
2. 微任务(Microtask)

定义:优先级较高的异步任务,总是在当前宏任务执行完毕后、下一个宏任务开始前执行。常见类型

  • Promise.then()Promise.catch()Promise.finally()
  • async/await(本质是 Promise 的语法糖,await 后的代码属于微任务)
  • queueMicrotask()(显式添加微任务)
  • process.nextTick()(Node 环境,优先级高于其他微任务)

三、执行顺序规则(核心)

  1. 同步代码优先:执行栈先执行所有同步代码,直到栈为空。
  2. 清空微任务队列:执行栈清空后,立即执行所有微任务(按入队顺序),直到微任务队列为空。
  3. 执行一个宏任务:微任务执行完后,从宏任务队列中取出一个宏任务执行(执行其同步代码)。
  4. 重复步骤 2-3:每执行完一个宏任务,就清空一次微任务队列,再执行下一个宏任务,形成循环(称为 “事件循环 Event Loop”)。

image.png 最终输出顺序1 6 3 5 2 4

五、关键区别与注意点

  • 优先级:微任务 > 宏任务(同一轮事件循环中,微任务先于宏任务执行)。
  • 数量:每轮事件循环执行一个宏任务,但会执行所有微任务
  • async/await 细节:await 后面的代码会被包装成微任务,例如:

image.png

事件循环Event Loop执行机制

  • 1.进入到script标签,就进入到了第一次事件循环.

  • 2.遇到同步代码,立即执行

  • 3.遇到宏任务,放入到宏任务队列里.

  • 4.遇到微任务,放入到微任务队列里.

  • 5.执行完所有同步代码

  • 6.执行微任务代码

  • 7.微任务代码执行完毕,本次队列清空

  • 寻找下一个宏任务,重复步骤1

    • 以此反复直到清空所以宏任务,这种不断重复的执行机制,就叫做事件循环

总结

  • 所有同步任务都在主线程上执行,形成一个执行栈(call stack)。

  • 遇到异步任务, 进入异步处理模块并注册回调函数; 等到指定的事件完成(如ajax请求响应返回, setTimeout延迟到指定时间)时,异步处理模块会将这个回调函数移入异步任务队列。

  • 当栈中的代码执行完毕,执行栈中的任务为空时,主线程会先检查微任务队列中是否有任务,如果有,就将微任务队列中的所有任务依次执行,直到微任务队列为空; 之后再检查宏任务队列中是否有任务,如果有,则取出第一个宏任务加入到执行栈中,之后再清空执行栈,检查微任务,以此循环,直到全部的任务都