动态

详情 返回 返回

前端設計模式深度解讀:從混沌到有序,寫出可維護的代碼 - 动态 详情

前端設計模式深度解讀:從混沌到有序,寫出可維護的代碼

前言:你是否也被這些代碼問題折磨過?

"這個彈窗組件改一處就崩三處,到底誰寫的?"

"為什麼同樣的表單驗證邏輯,每個頁面都要複製粘貼?"

"狀態管理越來越亂,新增功能要改五六個地方?"

"接手的項目像一團亂麻,根本不敢動核心邏輯?"

前端開發中,"能跑就行" 的代碼在初期或許能快速交付,但隨着項目迭代,維護成本會指數級增長。設計模式不是銀彈,卻能幫我們建立 "可預測、可複用、可擴展" 的代碼結構。本文從前端實際場景出發,通過 "問題驅動 + 代碼重構 + 場景對比" 的方式,解析 6 種最實用的設計模式,讓你從 "被動改 bug" 到 "主動控設計"。

一、設計模式基礎:不是炫技,而是解決問題的套路

很多開發者覺得設計模式是 "高端炫技",其實它的本質是前人總結的代碼組織經驗—— 當某種問題反覆出現時,形成的通用解決方案。前端領域的設計模式更注重 "輕量化",不需要嚴格遵循後端的經典實現,而是結合 JS 特性靈活應用。

1.1 為什麼前端需要設計模式?

前端項目的複雜度主要來自三個方面:

  • 交互邏輯膨脹:從簡單表單到複雜交互,狀態管理越來越亂
  • 組件複用困境:複製粘貼導致的 "一處修改,處處修改"
  • 團隊協作成本:每個人風格不同,代碼可讀性差

設計模式通過以下方式解決這些問題:

1 . 封裝變化點 → 讓穩定部分和變化部分分離

2 . 定義清晰接口 → 組件/模塊間通信有章可循

3 . 複用成熟方案 → 減少重複思考,降低協作成本

1.2 前端常用設計模式分類

前端場景中,以下三大類設計模式使用頻率最高:

類型 核心目標 前端常用模式 典型應用場景
創建型 優化對象創建過程 單例模式、工廠模式、建造者模式 全局狀態、組件工廠、表單生成
結構型 優化對象組合關係 代理模式、裝飾器模式、適配器模式 緩存代理、權限控制、接口適配
行為型 優化對象交互行為 觀察者模式、策略模式、迭代器模式 事件監聽、表單驗證、列表渲染

這些模式不是孤立的,實際開發中常組合使用(如 "工廠模式 + 策略模式" 處理動態表單)。

二、創建型模式:讓對象創建更可控

創建型模式解決的核心問題是:如何避免硬編碼創建對象,讓創建過程更靈活、可配置

2.1 單例模式:全局唯一的 "狀態管家"

核心問題:確保一個類只有一個實例,並提供全局訪問點。

場景痛點:
// 問題代碼:多次創建導致狀態不一致

// 第一次創建彈窗實例

const modal1 = new Modal();

modal1.show();

// 其他地方再次創建

const modal2 = new Modal();

modal2.hide(); // 此時modal1的狀態不會同步變化
解決方案:

通過閉包保存實例,確保只創建一次:

class Modal {

      constructor() {

        // 防止通過new創建多個實例

        if (Modal.instance) {

          return Modal.instance;

        }

        Modal.instance = this;

        this.visible = false;

      }

      show() {

        this.visible = true;

        // 渲染邏輯...

      }

      hide() {

        this.visible = false;

        // 隱藏邏輯...

      }

      // 靜態方法獲取實例(推薦)

      static getInstance() {

        if (!Modal.instance) {

          Modal.instance = new Modal();

        }

        return Modal.instance;

      }

}

// 使用方式

const modal1 = Modal.getInstance();

const modal2 = Modal.getInstance();

console.log(modal1 === modal2); // true(同一實例)
前端典型應用:
  • Vuex 的store、Redux 的store(全局唯一狀態容器)
  • 全局事件總線(EventBus
  • 瀏覽器的windowdocument對象(天然單例)
避坑點:
  • ❌ 不要濫用單例:頻繁使用會導致代碼耦合度升高
  • ✅ 適合場景:全局狀態管理、資源池(如請求池)、工具類

2.2 工廠模式:批量創建對象的 "生產線"

核心問題:用統一接口創建不同類型的對象,隱藏創建細節。

場景痛點:
// 問題代碼:創建不同表單組件時硬編碼判斷

function createFormItem(type, options) {

      let item;

      if (type === 'input') {

        item = new Input(options);

      } else if (type === 'select') {

        item = new Select(options);

      } else if (type === 'checkbox') {

        item = new Checkbox(options);

      } else {

        throw new Error('未知組件類型');

      }

      return item;

}

當新增組件類型時,需要修改createFormItem函數,違反 "開放 - 封閉原則"。

解決方案:

用工廠類管理創建邏輯,新增類型只需擴展工廠:

// 1. 定義組件基類

class FormItem {

      constructor(options) {

        this.name = options.name;

        this.label = options.label;

      }

}

// 2. 具體組件實現

class Input extends FormItem { / * ...  */ }

class Select extends FormItem { / * ...  */ }

class Checkbox extends FormItem { / * ...  */ }

// 3. 工廠類

class FormItemFactory {

      // 註冊組件類型

      static registry = {

        input: Input,

        select: Select,

        checkbox: Checkbox

      };

      // 創建組件

      static create(type, options) {

        const Component = this.registry [type];

        if (!Component) {

          throw new Error( `未知組件類型: ${type} `);

        }

        return new Component(options);

      }

      // 擴展新組件(無需修改工廠核心代碼)

      static register(type, Component) {

        this.registry [type] = Component;

      }

}

// 使用方式

const usernameInput = FormItemFactory.create('input', {

      name: 'username',

      label: '用户名'

});

// 新增組件時只需註冊,無需修改create方法

FormItemFactory.register('radio', Radio);

const genderRadio = FormItemFactory.create('radio', { / * ...  */ });
前端典型應用:
  • Vue 的createElement函數(創建不同 VNode)
  • 路由工廠(根據路由配置創建不同頁面組件)
  • 圖表庫(根據類型創建折線圖、柱狀圖等)

三、結構型模式:讓對象組合更靈活

結構型模式解決的核心問題是:如何通過對象組合實現功能擴展,而不是硬編碼繼承

3.1 代理模式:控制訪問的 "中間層"

核心問題:給目標對象提供代理對象,控制對目標對象的訪問(如緩存、權限、防抖)。

場景痛點:
// 問題代碼:頻繁調用接口導致性能問題

function fetchUserInfo(userId) {

      return fetch( `/api/user/ ${userId} `).then(res => res.json());

}

// 短時間內多次調用

fetchUserInfo(1);

fetchUserInfo(1); // 重複請求,浪費資源

fetchUserInfo(1);
解決方案:

用代理模式添加緩存層,避免重複請求:

// 1. 原始接口函數

function fetchUserInfo(userId) {

      return fetch( `/api/user/ ${userId} `).then(res => res.json());

}

// 2. 創建緩存代理

function createCacheProxy(fn) {

      const cache = new Map(); // 緩存容器



      return function proxy(userId) {

        // 命中緩存直接返回

        if (cache.has(userId)) {

          console.log('使用緩存數據');

          return Promise.resolve(cache.get(userId));

        }



        // 未命中則請求並緩存結果

        return fn(userId).then(data => {

          cache.set(userId, data);

          return data;

        });

      };

}

// 3. 使用代理

const proxyFetchUser = createCacheProxy(fetchUserInfo);

// 調用測試

proxyFetchUser(1); // 發起請求

proxyFetchUser(1); // 使用緩存

proxyFetchUser(1); // 使用緩存
前端典型應用:
  • Vue 的響應式系統(Proxy代理對象實現數據劫持)
  • 圖片懶加載(代理圖片加載過程,延遲真實請求)
  • 防抖節流(代理事件處理函數,控制執行頻率)
  • 權限控制(代理按鈕點擊事件,無權限時阻止執行)

3.2 裝飾器模式:動態擴展功能的 "插件系統"

核心問題:不修改原有對象,動態給對象添加新功能(比繼承更靈活)。

場景痛點:
// 問題代碼:用繼承擴展功能導致類爆炸

class Button {

      click() {

        console.log('按鈕點擊');

      }

}

// 需要添加日誌功能

class LogButton extends Button {

      click() {

        super.click();

        console.log('記錄點擊日誌');

      }

}

// 需要添加埋點功能

class TrackButton extends Button {

      click() {

        super.click();

        console.log('發送埋點數據');

      }

}

// 需要同時添加日誌和埋點 → 被迫創建新類

class LogTrackButton extends LogButton {

      click() {

        super.click();

        console.log('發送埋點數據'); // 重複代碼

      }

}
解決方案:

用裝飾器動態組合功能,避免類爆炸:

class Button {

      click() {

        console.log('按鈕點擊');

      }

}

// 1. 日誌裝飾器

function withLog(button) {

      const originalClick = button.click;

      button.click = function() {

        originalClick.call(this);

        console.log('記錄點擊日誌');

      };

      return button;

}

// 2. 埋點裝飾器

function withTrack(button) {

      const originalClick = button.click;

      button.click = function() {

        originalClick.call(this);

        console.log('發送埋點數據');

      };

      return button;

}

// 3. 動態組合功能

const button = new Button();

const logButton = withLog(button);

const logTrackButton = withTrack(logButton); // 疊加裝飾

logTrackButton.click();

// 輸出:

// 按鈕點擊

// 記錄點擊日誌

// 發送埋點數據
前端典型應用:
  • React 的 HOC(高階組件)如withRouterconnect
  • Vue 的mixin(雖然有缺陷,但本質是裝飾思想)
  • 類組件的生命週期擴展(如添加統一的錯誤處理)
  • TypeScript/ES7 的@decorator語法(更優雅的裝飾器實現)

四、行為型模式:讓對象交互更有序

行為型模式解決的核心問題是:如何規範對象間的通信方式,減少耦合

4.1 觀察者模式:解耦通信的 "發佈 - 訂閲" 機制

核心問題:當一個對象狀態變化時,自動通知所有依賴它的對象(解耦發佈者和訂閲者)。

場景痛點:
// 問題代碼:組件間通信硬編碼,耦合嚴重

class Header {

      constructor(cart) {

        this.cart = cart; // 直接依賴Cart

      }



      update() {

        // 顯示購物車數量

        this.render(this.cart.count);

      }

}

class Cart {

      constructor() {

        this.count = 0;

        this.header = new Header(this); // 強耦合

      }



      addItem() {

        this.count++;

        this.header.update(); // 直接調用Header方法

      }

}

當新增一個需要監聽購物車變化的Footer組件時,必須修改Cart類。

解決方案:

用觀察者模式實現鬆耦合通信:

// 1. 發佈者基類

class Subject {

      constructor() {

        this.observers =  []; // 訂閲者列表

      }



      // 訂閲

      subscribe(observer) {

        this.observers.push(observer);

      }



      // 取消訂閲

      unsubscribe(observer) {

        this.observers = this.observers.filter(o => o !== observer);

      }



      // 通知所有訂閲者

      notify(data) {

        this.observers.forEach(observer => observer.update(data));

      }

}

// 2. 購物車(發佈者)

class Cart extends Subject {

      constructor() {

        super();

        this.count = 0;

      }



      addItem() {

        this.count++;

        this.notify(this.count); // 通知所有訂閲者

      }

}

// 3. 訂閲者:Header

class Header {

      update(count) {

        console.log( `Header顯示購物車數量: ${count} `);

      }

}

// 4. 訂閲者:Footer

class Footer {

      update(count) {

        console.log( `Footer顯示購物車數量: ${count} `);

      }

}

// 使用方式

const cart = new Cart();

const header = new Header();

const footer = new Footer();

// 訂閲

cart.subscribe(header);

cart.subscribe(footer);

// 觸發更新

cart.addItem(); // Header和Footer同時更新
前端典型應用:
  • DOM 事件機制(addEventListener本質是觀察者模式)
  • Vue 的響應式系統(數據變化通知組件更新)
  • Redux 的subscribe(狀態變化通知 UI 更新)
  • 事件總線(EventBus)的實現

4.2 策略模式:優化條件判斷的 "算法家族"

核心問題:將一系列算法封裝起來,使其可互相替換,解決複雜條件判斷問題。

場景痛點:
// 問題代碼:表單驗證邏輯充滿if-else

function validateForm(formData, formType) {

      if (formType === 'login') {

        if (!formData.username) return '用户名不能為空';

        if (!formData.password) return '密碼不能為空';

        if (formData.password.length < 6) return '密碼至少6位';

      } else if (formType === 'register') {

        if (!formData.username) return '用户名不能為空';

        if (!formData.email) return '郵箱不能為空';

        if (!/^ [^ s@]+@ [^ s@]+  . [^ s@]+ $/.test(formData.email)) return '郵箱格式錯誤';

        if (!formData.password) return '密碼不能為空';

        // ...更多驗證

      } else if (formType === 'profile') {

        // ...另一套驗證邏輯

      }

      return '驗證通過';

}

條件越多,代碼越難維護,新增表單類型需要修改函數內部。

解決方案:

用策略模式封裝不同驗證邏輯,消除條件判斷:

// 1. 定義驗證策略

const validationStrategies = {

      login: (data) => {

        if (!data.username) return '用户名不能為空';

        if (!data.password) return '密碼不能為空';

        if (data.password.length < 6) return '密碼至少6位';

        return null; // 驗證通過

      },



      register: (data) => {

        if (!data.username) return '用户名不能為空';

        if (!data.email) return '郵箱不能為空';

        if (!/^ [^ s@]+@ [^ s@]+  . [^ s@]+ $/.test(data.email)) return '郵箱格式錯誤';

        // ...其他驗證

        return null;

      },



      profile: (data) => {

        // ...個人資料驗證邏輯

        return null;

      }

};

// 2. 驗證器(使用策略)

function validateForm(formData, formType) {

      const strategy = validationStrategies [formType];

      if (!strategy) throw new Error( `未知表單類型: ${formType} `);

      return strategy(formData) || '驗證通過';

}

// 3. 使用方式

const loginData = { username: 'alice', password: '123' };

console.log(validateForm(loginData, 'login')); // 密碼至少6位

// 4. 新增策略(無需修改驗證器)

validationStrategies.forgotPassword = (data) => {

      if (!data.email) return '郵箱不能為空';

      return null;

};
前端典型應用:
  • 表單驗證(不同表單用不同驗證策略)
  • 支付方式選擇(不同支付方式用不同處理策略)
  • 主題切換(不同主題用不同樣式策略)
  • 排序算法選擇(不同場景用不同排序策略)

五、設計模式實戰:從 0 到 1 構建可擴展組件

以 "動態表單系統" 為例,展示如何組合多種設計模式解決複雜問題。

5.1 需求分析

需要實現一個支持多種表單類型(登錄、註冊、個人信息)、可動態添加字段、帶驗證功能、支持表單提交埋點的系統。

5.2 模式組合方案

1 . 工廠模式 → 創建不同類型的表單組件

2 . 策略模式 → 處理不同表單的驗證邏輯

3 . 觀察者模式 → 表單狀態變化通知UI更新

4 . 裝飾器模式 → 給表單添加提交埋點功能

5.3 核心代碼實現

// 1. 工廠模式:創建表單字段

class FormFieldFactory {

      static create(type, options) {

        const fields = {

          input: () => new InputField(options),

          select: () => new SelectField(options),

          checkbox: () => new CheckboxField(options)

        };

        return fields [type] ? fields [type] () : null;

      }

}

// 2. 策略模式:表單驗證

const validationStrategies = {

      login: (values) => { / * 登錄驗證  */ },

      register: (values) => { / * 註冊驗證  */ }

};

// 3. 觀察者模式:表單狀態管理

class FormSubject extends Subject {

      setValue(name, value) {

        this.values [name] = value;

        this.notify(this.values); // 通知UI更新

      }

}

// 4. 裝飾器模式:添加埋點功能

function withTracking(form) {

      const originalSubmit = form.submit;

      form.submit = function() {

        originalSubmit.call(this);

        console.log('發送表單提交埋點', this.type);

      };

      return form;

}

// 5. 組合使用

class DynamicForm {

      constructor(type, fieldsConfig) {

        this.type = type;

        this.subject = new FormSubject();

        this.fields = fieldsConfig.map(config =>

          FormFieldFactory.create(config.type, config)

        );

        this.validate = validationStrategies [type];

        withTracking(this); // 添加埋點

      }



      submit() {

        const error = this.validate(this.subject.values);

        if (!error) {

          console.log('表單提交成功');

        }

      }

}

// 使用示例

const loginForm = new DynamicForm('login',  [

      { type: 'input', name: 'username', label: '用户名' },

      { type: 'input', name: 'password', label: '密碼', type: 'password' }

]);

六、避坑指南:設計模式不是銀彈

設計模式雖好,但濫用會導致代碼過度設計,記住以下原則:

6.1 不要過早引入設計模式

"YAGNI 原則"(You Aren't Gonna Need It):在問題明確前,不要急於套用模式。

✅ 正確時機:當相同問題出現 3 次以上,且複製粘貼開始影響維護時。

6.2 避免過度設計

// 反面例子:給簡單工具函數用工廠模式

// 完全沒必要,直接導出函數更簡單

class StringUtilFactory {

      static create(type) {

        if (type === 'trim') return new TrimUtil();

        // ...

      }

}

// 正確做法:直接導出工具函數

const StringUtils = {

      trim(str) { / * ...  */ }

};

6.3 優先使用 JS 特性簡化實現

JS 的動態特性(閉包、高階函數、對象字面量)可以簡化很多模式:

  • 用對象字面量代替簡單工廠
  • 用高階函數代替裝飾器類
  • 用數組方法代替迭代器模式

6.4 關注問題而非模式名稱

不要為了 "用模式而用模式",比如:

  • 不是所有全局對象都需要單例模式(簡單的export const可能更合適)
  • 不是所有條件判斷都需要策略模式(少量條件用switch更清晰)

七、總結:設計模式的本質是 "權衡"

前端設計模式的核心價值不是 "寫出優雅的代碼",而是通過成熟的結構解決特定問題,同時讓團隊協作更高效。

掌握設計模式的三個階段:

  1. 模仿階段:遇到問題時,回憶哪種模式能解決
  2. 應用階段:根據場景靈活調整模式實現,不生搬硬套
  3. 內化階段:不需要刻意想模式,寫出的代碼自然符合模式思想

最後記住:最好的代碼是 "恰到好處" 的代碼 —— 既不過度設計,也不雜亂無章。設計模式是幫我們找到這個平衡點的工具,而不是束縛思維的教條。當你能自如地在 "簡單直接" 和 "靈活可擴展" 之間做出權衡時,就真正掌握了設計模式的精髓。總而言之,一鍵點贊、評論、喜歡收藏吧!這對我很重要!

user avatar grewer 头像 cyzf 头像 alibabawenyujishu 头像 zaotalk 头像 smalike 头像 banana_god 头像 yishidemeihao_5b9ce075877c9 头像 xiaolei_599661330c0cb 头像 DingyLand 头像 joe235 头像 it1042290135 头像 lovecola 头像
点赞 75 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.