Skip to content

设计模式、Web Vitals 与性能优化 ​

作者:青见春山

前端面试准备中关于设计模式、Web Vitals 与性能优化的知识点整理。

设计模式 ​

前端面试必备设计模式 ​

1. 观察者模式 ⭐⭐⭐ ​

一句话:一个对象变了,自动通知所有关注者。

Vue 的响应式原理就是它:数据变化 → 自动更新视图。

JavaScript
// 手写简易版 —— 面试高频题
class Dep {
  constructor() { this.subs = []; }
  addSub(watcher) { this.subs.push(watcher); }
  notify() { this.subs.forEach(w => w.update()); }
}

class Watcher {
  constructor(cb) { this.cb = cb; }
  update() { this.cb(); }
}

面试关键词:Vue 响应式、EventEmitter、DOM 事件


2. 单例模式 ⭐⭐⭐ ​

Image

Image

答案为true

一句话:全局只能有一个实例。 后续无法再更改

典型场景:Vuex/Redux 的 Store、全局弹窗、登录框。

JavaScript
// 面试手写
class Singleton {
  static instance = null;
  static getInstance() {
    if (!Singleton.instance) Singleton.instance = new Singleton();
    return Singleton.instance;
  }
}

3. 代理模式 ⭐⭐⭐ ​

Image

类似于经纪人之于明星,谈戏要先经过经纪人看是否接戏

Image

Image

一句话:提供一个替身控制对原对象的访问。

Vue 3 的核心:用 Proxy 实现响应式,拦截 get/set。

JavaScript
const obj = new Proxy(data, {
  get(target, key) { /* 收集依赖 */ return target[key]; },
  set(target, key, val) { target[key] = val; /* 触发更新 */ return true; }
});

面试关键词:Vue 3 Proxy、图片懒加载、缓存代理

vue3数据驱动视图更新简单实现:

Image

注意要修改的是proxy的属性


4. 工厂模式 ⭐⭐ ​

一句话:传入参数,返回不同类型的对象,隐藏创建细节。

典型场景:根据 type 创建不同组件、React.createElement 本质就是工厂。

JavaScript
function createComponent(type) {
  const map = { header: Header, footer: Footer, sidebar: Sidebar };
  return new map[type]();
}

Image

Image

加static就可以不用实例化而直接调用

Image

抽象工厂模式(Abstract Factory) ​

其实就是抽取了一个父类,然后不同的参数得到不同的子类实例化,父类又可以增加共同的属性和方法,子类进行继承,子类有自己的属性和方法

Image

Image

一句话理解 ​

普通工厂:一个工厂生产一种产品

抽象工厂:一个工厂生产一族相关产品


核心思想 ​

提供一个接口,用于创建相互关联的一组对象,而不需要指定具体类。


最通俗的例子 ​

想象你去买车:

普通工厂抽象工厂
问题只能造一个零件一辆车需要发动机+轮胎+座椅,它们必须配套
解决造发动机的工厂宝马工厂 → 宝马发动机 + 宝马轮胎 + 宝马座椅
造轮胎的工厂奔驰工厂 → 奔驰发动机 + 奔驰轮胎 + 奔驰座椅

抽象工厂保证了:同一族的产品是配套的,不会出现"宝马发动机配奔驰轮胎"的混乱。


代码示例 ​

JavaScript
// 抽象工厂:定义接口
class UIFactory {
  createButton() {}
  createInput() {}
  createModal() {}
}

// 具体工厂1:暗色主题
class DarkThemeFactory extends UIFactory {
  createButton() { return { color: '#fff', bg: '#333' }; }
  createInput()  { return { border: '1px solid #555', bg: '#222' }; }
  createModal()  { return { bg: '#1a1a1a', shadow: 'none' }; }
}

// 具体工厂2:亮色主题
class LightThemeFactory extends UIFactory {
  createButton() { return { color: '#000', bg: '#fff' }; }
  createInput()  { return { border: '1px solid #ccc', bg: '#fafafa' }; }
  createModal()  { return { bg: '#ffffff', shadow: '0 2px 8px rgba(0,0,0,.15)' }; }
}

// 使用:切换主题只需换工厂,所有组件自动配套
function renderUI(factory) {
  const button = factory.createButton();
  const input  = factory.createInput();
  const modal  = factory.createModal();
  // ...渲染页面
}

renderUI(new DarkThemeFactory());   // 暗色主题
renderUI(new LightThemeFactory());  // 亮色主题

和普通工厂的区别(面试必答) ​

工厂方法抽象工厂
生产什么单个产品一族相关产品
工厂数量每个产品一个工厂一个工厂造多个配套产品
扩展方式新增产品 → 新增工厂新增产品族 → 新增工厂
耦合度较低保证产品族内部一致

前端实际应用 ​

场景说明
主题切换暗色/亮色主题,按钮、输入框、弹窗等必须风格统一
跨平台UI一套代码适配 Web / Mobile / Desktop,每个平台有不同的组件工厂
多数据库适配后端接口抽象,一个工厂出 MySQL/MongoDB/Redis 的连接+查询+缓存
CSS-in-JS 主题styled-components 的 ThemeProvider 本质就是抽象工厂

面试答题模板 ​

"抽象工厂模式提供一个接口,用于创建一族相关或相互依赖的对象。

比如前端的主题系统——暗色工厂同时生产暗色按钮、暗色输入框、暗色弹窗,保证风格一致。切换主题时只需切换工厂,所有组件自动配套,符合开闭原则。"

5. 策略模式 ⭐⭐ ​

一句话:把不同的算法封装起来,可以互相替换。

典型场景:表单验证(最常考)。

JavaScript
const rules = {
  required: v => v !== '' || '不能为空',
  email: v => /\S+@\S+\.\S+/.test(v) || '邮箱格式不对',
  minLength: (v, n) => v.length >= n || `至少${n}位`,
};

function validate(value, ruleList) {
  return ruleList.map(r => rules[r.type](value, r.param)).filter(Boolean);
}

速记口诀 ​

「观代单工厂,策略不难记」

  • 观察者 → Vue 响应式、事件
  • 代理 → Vue 3 Proxy
  • 单例 → 全局 Store
  • 工厂 → 动态创建组件
  • 策略 → 表单验证

面试怎么答? ​

被问到设计模式时,不要背定义,直接说:

"以观察者模式为例,Vue 的响应式系统就是典型应用——当数据变化时,所有依赖这个数据的组件会自动更新,底层通过 Dep 收集依赖、Watcher 触发更新来实现。"

先说模式 → 再说场景 → 最后说框架中的应用,这个三段式回答最加分。

好的,下面为你总结发布订阅模式和装饰器模式:


6.发布订阅模式(Publish-Subscribe Pattern) ​

核心思想 ​

发布者(Publisher)和订阅者(Subscriber)之间通过一个事件中心(Event Channel)进行解耦。发布者只负责发送事件,订阅者只负责监听和处理事件,两者互不直接引用。

角色 ​

角色职责
Publisher(发布者)发布事件/消息
Subscriber(订阅者)注册感兴趣的事件,收到通知后执行回调
EventChannel(事件中心)管理订阅关系,负责事件分发

简单实现 ​

JavaScript
class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  emit(event, ...args) {
    const callbacks = this.events[event] || [];
    callbacks.forEach(cb => cb(...args));
  }

  off(event, callback) {
    this.events[event] = (this.events[event] || []).filter(cb => cb !== callback);
  }
}

使用场景 ​

  • GUI 事件系统(按钮点击、键盘事件)

  • 消息队列(RabbitMQ、Kafka)

  • Vue 的事件总线(EventBus)

  • React 的状态管理(Redux 的 dispatch/subscribe)

  • WebSocket 消息推送

优点与缺点 ​

  • ✅ 松耦合:发布者和订阅者互不知道对方

  • ✅ 可扩展性强:新增订阅者无需修改发布者

  • ❌ 事件过多时难以追踪:调试和排查问题较困难

  • ❌ 可能存在内存泄漏:忘记取消订阅时


7.装饰器模式(Decorator Pattern) ​

Image

核心思想 ​

在不修改原始对象的情况下,通过将对象放入装饰器对象中来动态扩展其功能。装饰器提供了比继承更灵活的功能扩展方式。

角色 ​

角色职责
Component(组件接口)定义对象的统一接口
ConcreteComponent(具体组件)核心功能实现
Decorator(装饰器基类)持有组件引用,实现组件接口
ConcreteDecorator(具体装饰器)扩展具体功能

简单实现 ​

JavaScript
// 组件接口
class Coffee {
  cost() { return 10; }
  description() { return '基础咖啡'; }
}

// 装饰器:加牛奶
class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  cost() { return this.coffee.cost() + 5; }
  description() { return this.coffee.description() + ' + 牛奶'; }
}

// 装饰器:加糖
class SugarDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  cost() { return this.coffee.cost() + 3; }
  description() { return this.coffee.description() + ' + 糖'; }
}

// 使用:自由组合
const myCoffee = new SugarDecorator(new MilkDecorator(new Coffee()));
console.log(myCoffee.cost());        // 18
console.log(myCoffee.description()); // '基础咖啡 + 牛奶 + 糖'

使用场景 ​

  • Java I/O 流:new BufferedReader(new InputStreamReader(new FileInputStream(file)))

  • Python 装饰器语法:@decorator(语法糖形式)

  • TypeScript / ES7 装饰器:@log、@memoize 等

  • 中间件:Koa / Express 的洋葱模型

  • AOP(面向切面编程):日志、权限、缓存等横切关注点

优点与缺点 ​

  • ✅ 开闭原则:无需修改原有代码即可扩展功能

  • ✅ 灵活组合:可自由叠加多个装饰器

  • ✅ 单一职责:每个装饰器只关注自己的功能

  • ❌ 会产生很多小对象:系统复杂度增加

  • ❌ 多层装饰时排错困难

核心性能指标(Web Vitals) ​

现代前端关注的核心性能指标主要包括 Core Web Vitals(CWV):

三大核心指标(2024+) ​

指标全称衡量什么好的阈值说明
LCPLargest Contentful Paint最大内容绘制时间≤ 2.5s衡量加载性能,用户看到主要内容的时间
INPInteraction to Next Paint交互到下次绘制的延迟≤ 200ms衡量交互响应性,取代了 FID(2024年3月起)
CLSCumulative Layout Shift累积布局偏移≤ 0.1衡量视觉稳定性,页面元素是否乱跳

性能指标不仅关乎用户体验也关乎seo,google会把性能得分靠前的往前放。

Image

LCP ​

Image

通常一个页面的最大元素是主视觉图

LCP如果2.5s还没渲染,认为需要提示,4s还没渲染,认为有问题

设置优先级 ​

Image

INP ​

Image

点一次按钮,没有立即响应,会有一点点卡顿或者延迟

Image

FID只是第一次,已经被INP取代了

尽量不要做大量计算的工作,如果一定要做需要通过worker这种方式,避免阻塞主线程

CLS ​

Image

影响力分数乘以距离分数

影响力可以理解为一个元素越大,影响力就越大

距离分数是一个元素被挤得偏移的越远,距离得分就越大

Image

Image

Image

传统指标(仍有参考价值) ​

指标说明
FCP (First Contentful Paint)第一次有内容绘制,衡量首次加载速度
TTFB (Time to First Byte)服务器响应时间
FID (First Input Delay)首次输入延迟(已被 INP 取代)
TTI (Time to Interactive)页面完全可交互时间
TBT (Total Blocking Time)总阻塞时间,FCP 到 TTI 之间长任务的累计时间

如何理解这些指标 ​

想象你开了一家餐厅:

  • LCP = 顾客坐下后,第一道主菜端上来的时间

  • INP = 顾客喊服务员后,服务员响应的速度

  • CLS = 菜端上来后,桌子不会突然滑走/餐具不会乱飞

  • FCP = 顾客坐下后,先上一碟花生米(有东西可看)的时间

  • TTFB = 顾客点了菜后,厨房开始做的时间

监测工具 ​

  • Lighthouse(Chrome DevTools 内置)

  • Web Vitals 库(npm install web-vitals)

  • PageSpeed Insights(Google)

  • Chrome User Experience Report (CrUX)


性能优化 ​

Image

Image

白屏时间相对来说一般更长,其中资源加载时间是最长的

Image

异步加载:

分析哪些适合做,体积比较大,但又不是一开始就需要的功能,比如图片压缩功能引入一个比较大的库,这种就可以异步加载

更新为体积更小的新版本:

第三方库一般不会用到所有功能,treeshaking可以只打包用到的那些方法,但要看库支不支持,一般新版本就会支持,按需引入一般就是支持tree-shaking,减小代码体积,加快首屏渲染,但能不用三方库就不用

比如xlsx这个库

Image

Image

去除大的base64体积:

打包的时候图标会转base64,大的图片不要转base64

加骨架屏:

Image

vue和react都是打包之后出一个小的html文件,解析html再去请求js,那么可以先给一个骨架屏提高用户体验,因为业务问题无法再从技术上减少了

Image

Image

Image

4.请求粒度

Image

删除之后只需要请求列表,类目此时就是无效的请求

慎用keepalive,不更新带来的困扰比缓存带来的优化更多

Image

网络层:

  • DNS 预解析、HTTP/2、资源压缩(Gzip/Brotli)

  • CDN 加速、缓存策略(强缓存/协商缓存)

  • 按需加载、Tree Shaking

渲染层:

  • 骨架屏(首屏感知速度)

  • 图片懒加载、WebP 格式

  • CSS 动画代替 JS 动画(触发 GPU 合成层)

  • 减少重排重绘(合并 DOM 操作、transform 代替 top/left)

运行时:

  • 虚拟列表(长列表只渲染可视区域)

  • 防抖节流

  • Web Worker(复杂计算异步化)

  • 减少主线程阻塞


像送快递一样

主要从三个方向考虑:

传输更快,加载更快,体积更小 ​

升级带宽,加钱

Image

CDN内容分发网络,延迟低,也可以同时支持更多用户访问,但是双刃剑,按流量计费,很贵

浏览器缓存到用户本地,

Image

Image

Image

CDN缓存解决地理距离问题,浏览器缓存解决重复访问问题

Image

Image

Image

在web服务器进行开启,或CND一键开启HTTP2.0配置

Image

兼容性和稳定性有待验证

优化文件体积 ​

图片,音视频 jpg,gpg -》 webp

代码压缩,去除空格注释换行 打包工具会自动压缩优化tree shaking 或者用min压缩版本

网站传输时用gzip压缩

Image

浏览器发请求表示接受gzip压缩,服务器发生压缩后的文件,浏览器解压处理

web服务器添加gzip配置

网站加载优化 ​

Image

延迟加载--懒加载,适合长页面和图片较多的网站

Image

按需加载--分模块

Image

分词加载-先快后好

先让用户看到东西,减少等待焦虑感

缩略图 -低清小图,高清大图

渐进式加载--模糊浏览,逐渐清晰

预加载

在用户访问之前就把资源准备好,让体验更流畅

Image

比如文章列表,可以预加载几篇文章,把握好度

请求合并

浏览器对同一域名有并发请求数的限制,可能会产生请求排队和阻塞,导致数据加载耗时过长

Image

Image

小图标---雪碧图 用background-position决定显示哪个图标

性能分析工具 ​

lighthouse

Image

一键分析,进行性能评估并给出建议

Image

Image