浅浅看一下设计模式

20 阅读5分钟

前端开发中常用的设计模式主要分为创建型、结构型和行为型三类,下面是针对前端高频使用的模式,逐一讲解其概念、使用场景并提供可直接运行的 Demo。

一、创建型模式

这类模式主要解决「对象创建」相关的问题,让对象创建更灵活、更可控。

1. 单例模式 (Singleton)

核心概念:保证一个类仅有一个实例,并提供一个全局访问点。 使用场景

  • 全局缓存对象 - 弹窗/模态框组件(确保页面中只有一个实例)
  • Vuex/Pinia 的 store 实例 - 全局事件总线 Demo 代码
// 单例模式实现(通用版) 
class SingletonModal { 
    constructor(content) { 
        // 如果已有实例,直接返回 
        if (SingletonModal.instance) { 
            return SingletonModal.instance; 
        } 
        this.content = content; 
        this.element = null; 
        // 缓存实例 
        SingletonModal.instance = this; 
    } 
    // 创建弹窗 
    DOM render() { 
        if (this.element) 
        return this.element; 

        const modal = document.createElement('div'); 
        modal.style.cssText = `
            position: fixed; 
            top: 50%; 
            left: 50%; 
            transform: translate(-50%, -50%); 
            padding: 20px; 
            background: white; 
            border: 1px solid #ccc; 
            z-index: 9999; 
        `; 
        modal.textContent = this.content; 
        this.element = modal; 
        document.body.appendChild(modal); 
        return modal; 
    } 
    // 关闭弹窗 
    close() { 
        if (this.element) { 
            document.body.removeChild(this.element); 
            this.element = null; 
        } 
    } 
} 
// 测试:多次创建只会得到同一个实例 
const modal1 = new SingletonModal('这是第一个弹窗'); 
const modal2 = new SingletonModal('这是第二个弹窗'); 
console.log(modal1 === modal2); // true(验证单例) 
modal1.render(); // 渲染弹窗(内容是"这是第一个弹窗",因为实例复用) 
// modal1.close(); // 关闭弹窗 

2. 工厂模式 (Factory)

核心概念:定义一个创建对象的接口,让子类决定实例化哪一个类,将对象创建的逻辑封装起来。 使用场景

  • 根据不同参数创建不同类型的组件(如按钮、输入框)
  • 数据格式化工具(根据数据类型返回不同格式)
  • 网络请求适配器(根据环境选择 Axios/fetch)

Demo 代码

// 定义不同类型的组件类 
class Button { 
    constructor(text) { 
        this.text = text; 
        this.type = 'button'; 
    } 
    render() { 
        return `<button>${this.text}</button>`; 
    } 
} 
class Input { 
    constructor(placeholder) { 
        this.placeholder = placeholder; 
        this.type = 'input'; 
    } 
    render() { 
        return `<input type="text" placeholder="${this.placeholder}">`; 
    } 
} 
class Select { 
    constructor(options) { 
        this.options = options; 
        this.type = 'select'; 
    } 
    render() { 
        const optionsHtml = this.options.map(opt => `<option value="${opt.value}">${opt.label}</option>`).join(''); 
        return `<select>${optionsHtml}</select>`; 
    } 
} 
// 组件工厂类 
class ComponentFactory { 
    static createComponent(type, config) { 
        switch (type) { 
            case 'button': 
                return new Button(config.text); 
            case 'input': 
                return new Input(config.placeholder); 
            case 'select': 
                return new Select(config.options); 
            default: 
                throw new Error(`不支持的组件类型:${type}`); 
        } 
    } 
} 
// 测试:通过工厂创建不同组件 
const button = ComponentFactory.createComponent('button', { text: '提交' }); 
const input = ComponentFactory.createComponent('input', { placeholder: '请输入姓名' }); 
const select = ComponentFactory.createComponent('select', { options: [{ label: '男', value: 'male' }, { label: '女', value: 'female' }] }); 
console.log(button.render()); // <button>提交</button> 
console.log(input.render()); // <input type="text" placeholder="请输入姓名">
console.log(select.render()); // <select><option value="male">男</option><option value="female">女</option></select> 

二、结构型模式

这类模式关注对象的组合,优化类或对象的结构,提高代码的复用性和灵活性。

1. 代理模式 (Proxy)

核心概念:为另一个对象提供一个替身或占位符,以控制对这个对象的访问。 使用场景

  • 图片懒加载(代理控制图片加载时机)
  • 权限控制(代理拦截无权限的操作)
  • 数据缓存(代理缓存重复请求的结果)
  • Vue3 的响应式原理(基于 ES6 Proxy 实现) Demo 代码(图片懒加载)
// 真实的图片加载类 
class RealImage { 
    constructor(url) { 
        this.url = url; 
        this.loadImage(); 
    } 
    // 实际加载图片 
    loadImage() { 
        console.log(`开始加载图片:${this.url}`); 
        // 模拟图片加载耗时 
        setTimeout(() => { 
            console.log(`图片 ${this.url} 加载完成`); 
        }, 1000); 
    } 
    // 渲染图片 
    render(container) { 
        const img = document.createElement('img'); 
        img.src = this.url; 
        img.alt = '示例图片'; 
        container.appendChild(img); 
    } 
}
// 图片代理类(懒加载) 
class ImageProxy { 
    constructor(url) { 
        this.url = url; 
        this.realImage = null; 
        // 延迟创建真实图片实例 
        this.placeholder = 'https://via.placeholder.com/100x100?text=Loading'; // 占位图 
    } 
    // 代理渲染逻辑 
    render(container) { 
        // 先渲染占位图 
        const placeholderImg = document.createElement('img');
        placeholderImg.src = this.placeholder; 
        placeholderImg.alt = '加载中'; 
        container.appendChild(placeholderImg); 
        // 模拟滚动到可视区域后加载真实图片 
        setTimeout(() => { 
            this.realImage = new RealImage(this.url); 
            // 替换占位图
            container.removeChild(placeholderImg); 
            this.realImage.render(container); 
        }, 2000); 
    } 
} 
// 测试:使用代理加载图片 
const container = document.getElementById('image-container') || document.body; 
const imageProxy = new ImageProxy('https://picsum.photos/400/300');
imageProxy.render(container); 

2. 装饰器模式 (Decorator)

核心概念:动态地给一个对象添加一些额外的职责,而不改变其原有结构。 使用场景

  • 给组件添加额外功能(如按钮添加防抖、输入框添加校验)
  • 权限装饰(给不同角色的按钮添加不同操作权限)
  • 日志装饰(给函数添加日志记录功能)

Demo 代码(函数装饰器)

// 基础函数:提交表单 
function submitForm(data) { 
    console.log('提交表单数据:', data); 
    return { code: 200, msg: '提交成功' }; 
} 
// 装饰器1:防抖装饰 
function debounceDecorator(fn, delay = 500) { 
    let timer = null; 
    return function(...args) { 
        clearTimeout(timer); 
        timer = setTimeout(() => { fn.apply(this, args); }, delay); 
    }; 
} 
// 装饰器2:日志装饰 
function logDecorator(fn) { 
    return function(...args) { 
        console.log(`[${new Date().toLocaleString()}] 执行函数:${fn.name}`); 
        const result = fn.apply(this, args); 
        console.log(`[${new Date().toLocaleString()}] 函数执行结果:`, result); 
        return result; 
    }; 
} 
// 装饰器3:校验装饰 
function validateDecorator(fn) { 
    return function(...args) { 
        const data = args[0];
        if (!data || !data.username) { 
            console.error('校验失败:用户名不能为空'); 
            return { code: 400, msg: '用户名不能为空' }; 
        } 
        return fn.apply(this, args); 
    }; 
} 
// 组合装饰器:给提交函数添加防抖+日志+校验 
const decoratedSubmit = debounceDecorator(logDecorator(validateDecorator(submitForm)));
// 测试
decoratedSubmit({ username: '' }); // 校验失败
decoratedSubmit({ username: 'zhangsan', age: 20 }); // 防抖后执行,带日志 
decoratedSubmit({ username: 'zhangsan', age: 20 }); // 重复点击被防抖拦截 

三、行为型模式

这类模式关注对象之间的通信和交互,优化对象的行为逻辑。

1. 观察者模式 (Observer)

核心概念:定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。 使用场景

  • 事件监听(如 DOM 事件、自定义事件)
  • 发布订阅系统(如 Vue 的 EventBus)
  • 状态管理(如 React 状态更新、Vue 的响应式)
  • 消息通知系统

Demo 代码(自定义事件总线)

// 观察者模式实现(事件总线) 
class EventBus { 
    constructor() { 
        this.events = {}; // 存储事件和对应的回调函数 
    } 
    // 订阅事件 
    on(eventName, callback) { 
        if (!this.events[eventName]) { 
            this.events[eventName] = []; 
        } 
        this.events[eventName].push(callback); 
    } 
    // 取消订阅 
    off(eventName, callback) { 
        if (!this.events[eventName])
            return; 
        if (callback) { 
            // 移除指定回调 
            this.events[eventName] = this.events[eventName].filter(fn => fn !== callback); 
        } else { 
            // 清空该事件所有回调 
            this.events[eventName] = []; 
        }
    } 
    // 发布事件(触发) 
    emit(eventName, ...args) { 
        if (!this.events[eventName]) 
        return; 
        // 执行所有订阅的回调 
        this.events[eventName].forEach(callback => { callback.apply(this, args); });
    } 
    // 一次性订阅 
    once(eventName, callback) { 
        const wrapCallback = (...args) => { 
            callback.apply(this, args); 
            this.off(eventName, wrapCallback); // 执行后取消订阅 
        }; 
        this.on(eventName, wrapCallback); 
    } 
}
// 测试:使用事件总线 
const bus = new EventBus(); 

// 订阅事件 
const handleMsg = (msg) => { 
    console.log('收到消息:', msg); 
};
bus.on('message', handleMsg); 

// 一次性订阅 
bus.once('onceMsg', (msg) => { 
    console.log('一次性消息:', msg); 
});

// 发布事件 
bus.emit('message', 'Hello Observer Pattern'); // 收到消息:Hello Observer Pattern 
bus.emit('onceMsg', 'This is once'); // 一次性消息:This is once 
bus.emit('onceMsg', 'This will not be received'); // 无输出 

// 取消订阅 
bus.off('message', handleMsg); bus.emit('message', 'This will not be received'); // 无输出 

2. 策略模式 (Strategy)

核心概念:定义一系列算法,将每个算法封装起来,并使它们可以互相替换,让算法的变化独立于使用算法的客户端。 使用场景

  • 表单校验规则(不同字段用不同校验策略)
  • 支付方式选择(支付宝/微信/银行卡)
  • 排序算法切换(快速排序/冒泡排序)
  • 价格计算策略(普通用户/VIP/超级VIP)

Demo 代码(表单校验)

// 定义校验策略 
const validateStrategies = { 
    // 非空校验 
    required: (value, errorMsg) => { 
        if (value === '' || value === undefined || value === null) { 
            return errorMsg; 
        } 
    }, 
    // 最小长度校验 
    minLength: (value, length, errorMsg) => { 
        if (value && value.length < length) { 
            return errorMsg; 
        } 
    }, 
    // 手机号校验 
    mobile: (value, errorMsg) => { 
        const reg = /^1[3-9]\d{9}$/; 
        if (value && !reg.test(value)) { 
            return errorMsg; 
        } 
    }, 
    // 邮箱校验 
    email: (value, errorMsg) => { 
        const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; 
        if (value && !reg.test(value)) { 
            return errorMsg; 
        } 
    } 
}; 
// 校验器类 
class Validator { 
    constructor() { 
        this.rules = []; // 存储校验规则 
    } 
    // 添加校验规则 
    add(value, rules) { 
        rules.forEach(rule => { 
            const { strategy, errorMsg, param } = rule; 
            this.rules.push(() => { 
                // 拆分策略(如 minLength:6 拆分为 minLength 和 6) 
                const [strategyName, strategyParam] = strategy.split(':'); 
                return validateStrategies[strategyName](value, strategyParam || param, errorMsg); 
            }); 
        });
    } 
    // 执行校验 
    validate() { 
        for (const rule of this.rules) { 
            const errorMsg = rule(); 
            if (errorMsg) { 
                return errorMsg; // 有错误立即返回 
            } 
        } 
        return ''; // 校验通过 
    } 
} 
// 测试:表单校验 
const formData = { 
    username: '', 
    password: '123', 
    mobile: '1234567890', 
    email: 'test@example' 
}; 
// 创建校验器并添加规则 
const validator = new Validator(); 
validator.add(formData.username, [{ strategy: 'required', errorMsg: '用户名不能为空' }]);
validator.add(formData.password, [ { strategy: 'required', errorMsg: '密码不能为空' }, { strategy: 'minLength:6', errorMsg: '密码长度不能少于6位' } ]); 
validator.add(formData.mobile, [{ strategy: 'mobile', errorMsg: '手机号格式错误' }]);
validator.add(formData.email, [{ strategy: 'email', errorMsg: '邮箱格式错误' }]); 
// 执行校验 
const errorMsg = validator.validate(); 
console.log(errorMsg); // 用户名不能为空(第一个错误) 

总结

前端开发中高频使用的设计模式及核心要点:

  1. 单例模式:保证唯一实例,适用于全局组件/缓存/事件总线,核心是缓存实例并复用。
  2. 工厂模式:封装对象创建逻辑,适用于多类型组件/工具创建,核心是根据参数返回不同实例。
  3. 代理模式:控制对象访问,适用于懒加载/权限控制/缓存,核心是「替身」拦截并处理逻辑。
  4. 装饰器模式:动态扩展功能,适用于函数增强/组件扩展,核心是不修改原对象仅添加职责。
  5. 观察者模式:一对多通知,适用于事件系统/状态管理,核心是发布-订阅的解耦通信。
  6. 策略模式:算法封装与替换,适用于校验/支付/排序,核心是将算法与使用逻辑分离。 这些模式的核心价值是解耦、复用、可扩展,实际开发中不必生搬硬套,而是根据场景灵活运用,比如 Vue/React 框架内部就大量使用了这些模式(如 React 的合成事件用了观察者模式,Vue3 的响应式用了代理模式)。