目录

3. 单例模式

1. 核心定义

2. 单例模式的两种经典实现

3. 单例模式在Vuex中的实践

1. 为什么Store是“假单例”?

2. Vuex如何保证“应用内Store唯一”?

3. “假单例”的局限性

小结

4. 实践案例:全局弹窗管理器

场景说明

4.原型模式

1. 原型模式的定位:JS中的“编程范式”而非“可选设计模式”

2. 类中心语言 vs 原型中心语言对比

3. 原型与原型链:JS原型范式的核心

3.1 原型的三大核心关系

3.2 原型链的工作机制

4. 关键实践:对象深拷贝

4.1 深拷贝的核心需求

4.2 两种常见实现方式

5. 案例练习:基于原型模式创建“学生对象”并实现深拷贝

需求描述

3. 单例模式

1. 核心定义

单例模式是保证一个类仅有一个实例,并提供全局唯一访问点的设计模式。它的核心价值在于:

  • 避免重复创建实例导致的内存浪费(如全局状态容器、弹窗管理器);
  • 确保全局范围内实例的一致性(如配置对象、工具类)。
    该模式是前端面试高频考点,且在Vuex、Redux、jQuery等主流库中广泛应用。

2. 单例模式的两种经典实现

核心思路:通过“拦截实例创建”逻辑,确保无论调用多少次,仅返回首次创建的实例。

通过类的静态属性存储唯一实例,静态方法 getInstance 控制实例的创建与返回:

class SingleDog {
  show() {
    console.log('我是全局唯一的单例实例');
  }

  // 静态方法:全局访问入口
  static getInstance() {
    // 1. 判断实例是否已存在
    if (!SingleDog.instance) {
      // 2. 不存在则创建实例,挂载到类的静态属性上
      SingleDog.instance = new SingleDog();
    }
    // 3. 存在则直接返回
    return SingleDog.instance;
  }
}

// 测试:两次调用返回同一实例
const dog1 = SingleDog.getInstance();
const dog2 = SingleDog.getInstance();
console.log(dog1 === dog2); // true(证明实例唯一)

利用闭包的“私有变量”特性,隐藏实例状态(避免全局污染),仅暴露访问方法:

class SingleDog {
  show() {
    console.log('我是闭包维护的单例实例');
  }
}

// 立即执行函数(IIFE)创建闭包,保存实例状态
SingleDog.getInstance = (function() {
  // 闭包内的私有变量:仅内部可修改,外部无法访问
  let instance = null;

  // 返回访问函数(全局访问入口)
  return function() {
    if (!instance) {
      instance = new SingleDog();
    }
    return instance;
  };
})();

// 测试:结果仍为同一实例
const dog1 = SingleDog.getInstance();
const dog2 = SingleDog.getInstance();
console.log(dog1 === dog2); // true

3. 单例模式在Vuex中的实践

Vuex的Store(全局状态容器)是单例模式的典型应用,但并非“严格单例”,而是“假单例”——即“没有在Store类内部实现单例逻辑,却通过Vuex的整体设计保证同一Vue应用中Store唯一”。

1. 为什么Store是“假单例”?

Store类的构造函数无任何单例拦截逻辑,开发者可通过new关键字创建多个实例:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

// 手动创建两个不同的Store实例
const store1 = new Vuex.Store({ state: { count: 0 } });
const store2 = new Vuex.Store({ state: { count: 0 } });

console.log(store1 === store2); // false(证明Store类本身不限制实例数量)
2. Vuex如何保证“应用内Store唯一”?

Vuex通过两个核心逻辑,从“应用层面”确保Store唯一:

  • install函数:拦截多次插件安装
    Vuex是Vue插件,需通过Vue.use(Vuex)安装。install函数会判断Vue应用是否已安装过Vuex,若已安装则直接返回,避免重复注入:
let Vue; // 存储已安装Vuex的Vue实例

export function install(_Vue) {
  // 若已安装,直接拦截
  if (Vue && _Vue === Vue) {
    console.error('[vuex] 已安装,Vue.use(Vuex)只需调用一次');
    return;
  }
  // 未安装则标记,后续不再重复安装
  Vue = _Vue;
  // 注入Store到Vue实例(下一步逻辑)
  applyMixin(Vue);
}
  • vuexInit函数:组件树继承$store
    applyMixin会在Vue的beforeCreate生命周期注入vuexInit,让所有子组件继承根组件的$store,确保全应用访问同一实例:
function vuexInit() {
  const options = this.$options;
  // 根组件:直接挂载Store
  if (options.store) {
    this.$store = options.store;
  } 
    // 子组件:继承父组件的$store
  else if (options.parent && options.parent.$store) {
    this.$store = options.parent.$store;
  }
}
3. “假单例”的局限性

Store的唯一性仅针对同一Vue应用:若页面中存在多个Vue应用(如多个new Vue()),每个应用可拥有独立的Store实例。若需跨应用共享Store,仍需手动用单例模式封装。

小结

  1. 单例模式的核心是“唯一实例+全局访问”,实现关键是“拦截实例创建”;
  2. Vuex的Store是“假单例”:通过install拦截重复安装、vuexInit继承$store,确保应用内唯一;
  3. 单例模式的价值在于“节省内存+保证一致性”,适合全局工具类、状态容器、弹窗管理器等场景。

4. 实践案例:全局弹窗管理器

场景说明

前端应用中,弹窗(如提示框、确认框)通常需要“全局唯一”(避免多个弹窗叠加),适合用单例模式实现——确保无论调用多少次show,仅显示一个弹窗,且全局可通过统一方法控制。

// 全局弹窗管理器:单例模式(闭包实现)
const PopupManager = (function() {
  // 私有变量:存储弹窗DOM、实例状态
  let popupDOM = null;
  let instance = null;

  // 私有方法:创建弹窗DOM(仅内部调用)
  function createPopupDOM() {
    const div = document.createElement('div');
    div.style.cssText = `
      position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
      padding: 20px; background: #fff; border: 1px solid #eee; border-radius: 4px;
      z-index: 9999; display: none;
    `;
    document.body.appendChild(div);
    return div;
  }

  // 公有的实例方法
  function Popup() {
    this.popupDOM = popupDOM || createPopupDOM();
  }

  // 显示弹窗:接收弹窗内容
  Popup.prototype.show = function(content) {
    this.popupDOM.innerText = content;
    this.popupDOM.style.display = 'block';
  };

  // 隐藏弹窗
  Popup.prototype.hide = function() {
    this.popupDOM.style.display = 'none';
  };

  // 全局访问入口:确保实例唯一
  return {
    getInstance: function() {
      if (!instance) {
        instance = new Popup();
      }
      return instance;
    }
  };
})();

// 测试:两次调用getInstance,操作同一弹窗
const popup1 = PopupManager.getInstance();
const popup2 = PopupManager.getInstance();

popup1.show('这是第一个弹窗'); // 显示弹窗
setTimeout(() => {
  popup2.hide(); // 隐藏弹窗(证明操作的是同一实例)
}, 2000);
// 定义弹窗管理器的类型接口
interface IPopupManager {
  show(content: string): void;
  hide(): void;
}

class PopupManager implements IPopupManager {
  // 静态属性:存储唯一实例
  private static instance: PopupManager | null = null;
  // 私有属性:存储弹窗DOM
  private popupDOM: HTMLDivElement | null = null;

  // 私有构造函数:禁止外部通过new创建实例
  private constructor() {
    this.popupDOM = this.createPopupDOM();
  }

  // 私有方法:创建弹窗DOM
  private createPopupDOM(): HTMLDivElement {
    const div = document.createElement('div');
    div.style.cssText = `
      position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
      padding: 20px; background: #fff; border: 1px solid #eee; border-radius: 4px;
      z-index: 9999; display: none;
    `;
    document.body.appendChild(div);
    return div;
  }

  // 静态方法:全局访问入口
  public static getInstance(): PopupManager {
    if (!PopupManager.instance) {
      PopupManager.instance = new PopupManager();
    }
    return PopupManager.instance;
  }

  // 显示弹窗(实现IPopupManager接口)
  public show(content: string): void {
    if (this.popupDOM) {
      this.popupDOM.innerText = content;
      this.popupDOM.style.display = 'block';
    }
  }

  // 隐藏弹窗(实现IPopupManager接口)
  public hide(): void {
    if (this.popupDOM) {
      this.popupDOM.style.display = 'none';
    }
  }
}

// 测试:TypeScript类型安全的单例调用
const popup1 = PopupManager.getInstance();
const popup2 = PopupManager.getInstance();

popup1.show('TypeScript单例弹窗'); // 类型提示:content需为string
setTimeout(() => {
  popup2.hide(); // 操作同一弹窗实例
}, 2000);

// 错误:构造函数是私有的,禁止外部new
// const popup3 = new PopupManager(); // TS编译报错

4.原型模式

1. 原型模式的定位:JS中的“编程范式”而非“可选设计模式”

核心差异:Java等类中心语言中,原型模式是“特定场景工具”(仅当需要克隆对象避免重复传参时使用);而在JS中,原型模式是“面向对象的根基”——所有对象的创建、继承都依赖原型,是必选的编程范式

关键澄清:

  • JS使用原型的目的不是“克隆副本”,而是“共享数据/方法+创建对应类型实例”;
  • ES6 class 是原型继承的语法糖(MDN官方定义),并非全新的类模型,本质仍依赖 Prototype

2. 类中心语言 vs 原型中心语言对比

对比维度

类中心语言(如Java)

原型中心语言(如JavaScript)

对象创建基础

必须通过“类实例化”(new 类名(...)

通过“原型克隆”(Object.create/new 构造函数

原型模式的角色

可选工具(仅克隆场景用)

必选范式(所有对象依赖原型)

重复对象创建方式

重复传入相同参数(如两次new Dog(...)

克隆原型对象(无需重复传参)

3. 原型与原型链:JS原型范式的核心

3.1 原型的三大核心关系
  • 构造函数:拥有 prototype 属性,指向“原型对象”;
  • 原型对象:拥有 constructor 属性,指回“构造函数”,可挂载共享方法/属性;
  • 实例对象:拥有 __proto__ 属性(非标准但浏览器通用),创建时自动指向“构造函数的原型对象”。
// 构造函数
function Dog(name) { this.name = name; }
// 原型对象挂载共享方法
Dog.prototype.bark = () => console.log('汪汪');
// 实例对象
const wangcai = new Dog('旺财');

// 核心关系验证
console.log(wangcai.__proto__ === Dog.prototype); // true(实例→原型对象)
console.log(Dog.prototype.constructor === Dog); // true(原型对象→构造函数)

3.2 原型链的工作机制
  • 定义:访问实例的属性/方法时,会按以下顺序搜索,形成的轨迹即“原型链”:
    实例本身 → 实例.proto(原型对象) → 原型对象.proto(原型的原型) → ... → Object.prototype(链的顶端)
    若搜索至顶端仍未找到,返回 undefined

示例:wangcai.toString() 的执行逻辑:

  1. wangcai 实例:无 toString 方法;
  2. Dog.prototype:无 toString 方法;
  3. Object.prototype:存在 toString,执行该方法。

4. 关键实践:对象深拷贝

4.1 深拷贝的核心需求

解决“引用类型共享问题”:浅拷贝仅复制引用(如数组、对象),修改副本会影响原对象;深拷贝复制所有层级数据,副本与原对象完全独立。

4.2 两种常见实现方式

取巧方案JSON.stringify + JSON.parse

  • 优点:代码简洁,适合“纯JSON对象”(无函数、正则、循环引用);
  • 缺点:无法处理 functionRegExpDate、循环引用等场景。
function deepClone(obj) {
  // 处理值类型或null
  if (typeof obj !== 'object' || obj === null) return obj;
  // 区分数组和普通对象
  const copy = Array.isArray(obj) ? [] : {};
  // 遍历自有属性并递归拷贝
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key]);
    }
  }
  return copy;
}

核心逻辑:值类型(如数字、字符串)直接返回;引用类型(对象/数组)递归遍历,拷贝自有属性。

5. 案例练习:基于原型模式创建“学生对象”并实现深拷贝

需求描述
  1. 定义“学生”类型:包含 name(姓名)、age(年龄)、hobbies(爱好,数组)属性,以及 study(学习)方法(所有学生共享该方法,避免重复创建);
  2. 创建学生实例(如“小红”,爱好为 ['阅读', '编程']);
  3. 用深拷贝复制该实例,修改副本的 hobbies(如删除“阅读”),验证原实例与副本的引用类型属性是否独立。
// 1. 定义学生构造函数(原型挂载共享方法)
function Student(name, age, hobbies) {
  this.name = name;
  this.age = age;
  this.hobbies = hobbies;
}

// 共享方法:所有学生共用,不重复创建
Student.prototype.study = function() {
  console.log(`${this.name}正在学习,目标是掌握原型模式!`);
};

// 2. 实现深拷贝函数
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) return obj;
  const copy = Array.isArray(obj) ? [] : {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key]);
    }
  }
  return copy;
}

// 3. 测试逻辑
const xiaohong = new Student('小红', 17, ['阅读', '编程']);
const xiaohongCopy = deepClone(xiaohong);

// 修改副本的爱好(删除“阅读”)
xiaohongCopy.hobbies.splice(0, 1);

// 验证独立性:原实例与副本的hobbies互不影响
console.log('原实例爱好:', xiaohong.hobbies); // 输出:['阅读', '编程']
console.log('副本爱好:', xiaohongCopy.hobbies); // 输出:['编程']

// 验证原型方法共享
xiaohong.study(); // 输出:小红正在学习,目标是掌握原型模式!
xiaohongCopy.study(); // 输出:小红正在学习,目标是掌握原型模式!
// 1. 用 class 简化“原型+类型”:自带构造函数、原型方法、类型约束
class Student {
  // 直接声明属性(自动关联类型,不用单独写 interface)
  name: string;
  age: number;
  hobbies: string[];

  // 构造函数(不用手动定义 StudentConstructor 类型)
  constructor(name: string, age: number, hobbies: string[]) {
    this.name = name;
    this.age = age;
    this.hobbies = hobbies;
  }

  // 原型方法(自动挂载到 Student.prototype,不用手动赋值)
  study() {
    console.log(`${this.name}正在学习,目标是掌握原型模式!`);
  }
}

// 2. 简化泛型深拷贝(减少冗余断言,利用类型守卫)
function deepClone<T>(obj: T): T {
  // 处理值类型/null:直接返回(TS 自动推导类型)
  if (typeof obj !== 'object' || obj === null) return obj;

  // 区分数组/对象:用类型守卫减少 as 断言
  const copy = Array.isArray(obj) 
    ? obj.map(item => deepClone(item)) // 数组:递归拷贝每一项
    : Object.fromEntries( // 对象:遍历键值对递归拷贝
        Object.entries(obj).map(([key, val]) => [key, deepClone(val)])
      ) as T;

  return copy;
}

// 3. 测试逻辑(和原代码完全一致,功能不变)
const xiaohong = new Student('小红', 17, ['阅读', '编程']);
const xiaohongCopy = deepClone(xiaohong);

// 修改副本不影响原对象(深拷贝生效)
xiaohongCopy.hobbies.splice(0, 1);
console.log('原实例爱好:', xiaohong.hobbies); // ['阅读', '编程']
console.log('副本爱好:', xiaohongCopy.hobbies); // ['编程']

// 原型方法共享(class 自动挂载到原型)
xiaohong.study(); // 小红正在学习...
xiaohongCopy.study(); // 小红正在学习...
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐