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