news 2026/8/29 14:29:42

JavaScript对象创建模式:12种设计模式解析与实战应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript对象创建模式:12种设计模式解析与实战应用

1. 项目概述:为什么我们需要对象创建模式?

在JavaScript的世界里,对象是构建一切的基石。无论是前端页面上的一个交互组件,还是后端服务中的一个数据模型,最终都离不开对象的创建与管理。随着项目规模的扩大和复杂度的提升,如果还停留在使用简单的对象字面量{}或者new Object()来创建对象,代码很快就会变得难以维护、难以复用,甚至出现难以追踪的Bug。

“JS高级教程知识整理——12对象创建模式”这个标题,指向的正是解决这一系列痛点的核心知识体系。它不是教你如何写一个let obj = {name: ‘张三’},而是系统性地梳理了在JavaScript中,如何根据不同的场景、不同的需求,优雅、高效、安全地“生产”对象。这12种模式,就像是工厂里的12套不同模具和生产线,有的适合快速生产标准件(工厂模式),有的强调私有性和封装(模块模式),有的则专注于构建复杂对象的步骤(建造者模式)。

对于前端开发者而言,深入理解这些模式,意味着你能写出更健壮的框架代码、设计出更清晰的组件接口、更从容地进行代码重构。对于正在学习JS高级特性的朋友来说,这是从“会用”到“懂原理”、从“写功能”到“设计结构”的关键一跃。接下来,我将结合我多年的开发经验,为你逐一拆解这12种模式的精髓、适用场景以及那些官方文档里不会写的“坑”和技巧。

2. 对象创建模式的核心价值与设计哲学

在深入具体模式之前,我们必须先搞清楚一个根本问题:为什么要有这么多模式?直接new一下不香吗?这背后其实隐藏着软件工程的两个核心追求:可维护性可扩展性

想象一下,你正在开发一个电商网站的用户系统。最初,你可能会这样写:

let user1 = { name: ‘Alice’, age: 25, cart: [] }; let user2 = { name: ‘Bob’, age: 30, cart: [] };

当只有两个用户时,这没问题。但当你有成千上万个用户,并且每个用户都需要新增addressvipLevelloginHistory等属性,甚至每个用户对象都需要一个addToCart方法时,问题就来了。代码中会散落着大量重复的对象字面量,修改一个公共方法(比如修改addToCart的逻辑)会成为一场灾难。

对象创建模式的价值就在于,它通过一套约定俗成的“模板”或“流程”,将对象的创建过程规范化、抽象化。其核心设计哲学可以归纳为以下几点:

  1. 封装变化点:将对象创建过程中可能变化的部分(如对象的类型、初始配置、内部实现)隔离出来,避免其扩散到整个代码库。
  2. 提高复用性:创建逻辑被封装后,可以在多个地方复用,避免重复代码。
  3. 解耦客户端与具体类:使用对象的代码(客户端)不需要关心对象的具体类型和复杂的构建过程,它只依赖于一个抽象的接口或工厂,这使得替换对象类型变得非常容易。
  4. 控制实例化过程:有些模式(如单例模式)可以严格控制一个类只有一个实例;有些模式(如建造者模式)可以精细控制一个复杂对象的构建步骤。

理解这些哲学,能帮助我们在面对具体问题时,不是机械地套用模式,而是真正理解为何选择它。接下来,我们就进入正题,看看这12种模式如何各显神通。

3. 基础模式:从字面量到构造函数

在接触高级模式前,我们必须夯实基础。JavaScript提供了几种最原始的对象创建方式,它们是所有高级模式的基石。

3.1 对象字面量模式

这是最直接、最常用的方式,适用于创建一次性、不需要多个类似实例的对象,例如配置对象、命名空间对象。

// 配置对象 const appConfig = { apiBaseUrl: ‘https://api.example.com’, timeout: 5000, retryTimes: 3 }; // 命名空间对象,用于组织代码,避免全局污染 const MyApp = { utils: { formatDate(date) { /* ... */ }, debounce(fn, delay) { /* ... */ } }, components: {} };

注意:对象字面量创建的是单例。如果你需要多个具有相同结构但不同数据的实例,这不是最佳选择。

3.2 工厂函数模式

当需要创建多个类似对象时,工厂函数是第一步抽象。它封装了创建对象的细节,返回一个新的对象。

function createUser(name, age) { return { name, age, greeting() { console.log(`Hello, I‘m ${this.name}`); } }; } const user1 = createUser(‘Alice’, 25); const user2 = createUser(‘Bob’, 30);

优点:简单直观,避免了重复代码。缺点:每个对象都有自己的方法副本,造成了内存浪费。user1.greeting === user2.greeting的结果是false

3.3 构造函数模式

这是JavaScript面向对象编程的经典起点。通过new关键字调用构造函数,可以创建特定类型的对象。

function User(name, age) { // 实例属性 this.name = name; this.age = age; // 实例方法 - 不推荐这样定义! this.greeting = function() { console.log(`Hello, I‘m ${this.name}`); }; } const user1 = new User(‘Alice’, 25); const user2 = new User(‘Bob’, 30); console.log(user1 instanceof User); // true console.log(user1.greeting === user2.greeting); // false,同样存在内存浪费问题

关键机制new操作符做了四件事:1) 创建一个新空对象;2) 将这个新对象的原型 (__proto__) 指向构造函数的prototype属性;3) 将构造函数内部的this绑定到这个新对象;4) 执行构造函数代码;5) 如果构造函数没有返回其他对象,则返回这个新对象。

3.4 原型模式

为了解决构造函数模式中方法重复定义的内存浪费问题,我们利用JavaScript的原型链。将方法定义在构造函数的prototype对象上,所有实例共享这些方法。

function User(name, age) { this.name = name; this.age = age; } // 方法定义在原型上 User.prototype.greeting = function() { console.log(`Hello, I‘m ${this.name}`); }; User.prototype.updateAge = function(newAge) { this.age = newAge; }; const user1 = new User(‘Alice’, 25); const user2 = new User(‘Bob’, 30); console.log(user1.greeting === user2.greeting); // true,方法实现了共享

组合使用构造函数模式 + 原型模式是ES5时代最主流、最推荐的创建自定义类型的方式。构造函数用于定义实例属性,原型对象用于定义共享的方法和常量属性。这很好地模拟了传统面向对象语言中“类”的概念。

4. 进阶模式:应对复杂场景的设计

掌握了基础模式后,我们会遇到更复杂的场景:如何管理全局状态?如何创建一系列相关对象?如何构建一个特别复杂的对象?下面的模式提供了专业级的解决方案。

4.1 单例模式

确保一个类仅有一个实例,并提供一个全局访问点。在前端,常用于全局状态管理(如Vuex Store、Redux Store)、对话框、登录框等。

// ES5 实现(闭包方式) const Singleton = (function() { let instance; // 闭包内保存唯一实例 function createInstance() { const object = new Object(‘I am the instance’); return object; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // true // ES6+ 实现(类静态属性) class SingletonClass { constructor(data) { if (SingletonClass.instance) { return SingletonClass.instance; } this.data = data; SingletonClass.instance = this; } }

实操心得:在模块化开发中,利用ES6模块本身的“单例”特性是实现单例更简单的方式。一个模块无论被import多少次,都只会执行一次,导出的对象自然是单例。这比手动实现一个单例类更符合现代前端开发习惯。

4.2 工厂方法模式

定义一个用于创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。这在需要根据条件创建不同类实例时非常有用。

// 产品类 class Button { render() { console.log(‘渲染基础按钮’); } } class PrimaryButton extends Button { render() { console.log(‘渲染主要按钮’); } } class DangerButton extends Button { render() { console.log(‘渲染危险按钮’); } } // 创建者类(工厂) class Dialog { // 工厂方法 createButton() { // 这是一个抽象方法,本应抛出错误,JS中需子类实现 throw new Error(‘子类必须实现 createButton 方法’); } render() { const button = this.createButton(); button.render(); } } // 具体创建者 class PrimaryDialog extends Dialog { createButton() { return new PrimaryButton(); } } class DangerDialog extends Dialog { createButton() { return new DangerButton(); } } const primaryDialog = new PrimaryDialog(); primaryDialog.render(); // 输出:渲染主要按钮

应用场景:UI组件库中,根据不同的主题(如Ant Design的default/primary/danger)创建不同类型的按钮组件。

4.3 抽象工厂模式

提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。它是工厂方法模式的升级版,关注于产品族。

// 抽象产品接口 class UIFactory { createButton() {} createCheckbox() {} } // 具体工厂 - 现代风格 class ModernFactory extends UIFactory { createButton() { return new ModernButton(); } createCheckbox() { return new ModernCheckbox(); } } // 具体工厂 - 复古风格 class VintageFactory extends UIFactory { createButton() { return new VintageButton(); } createCheckbox() { return new VintageCheckbox(); } } // 客户端代码 function application(factory) { const button = factory.createButton(); const checkbox = factory.createCheckbox(); button.render(); checkbox.render(); } // 根据配置或环境选择工厂 const config = { theme: ‘modern’ }; const factory = config.theme === ‘modern’ ? new ModernFactory() : new VintageFactory(); application(factory);

与工厂方法的区别:工厂方法创建一种产品,抽象工厂创建一族产品。抽象工厂强调产品之间的兼容性(比如都是“现代风”的按钮和复选框)。

4.4 建造者模式

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。适用于构建过程复杂、步骤多、且可能需不同配置的对象。

class Pizza { constructor() { this.size = null; this.crust = ‘regular’; this.toppings = []; } describe() { console.log(`A ${this.size}-inch ${this.crust} crust pizza with ${this.toppings.join(‘, ‘)}`); } } class PizzaBuilder { constructor(size) { this.pizza = new Pizza(); this.pizza.size = size; } setCrust(crust) { this.pizza.crust = crust; return this; // 返回this以实现链式调用 } addTopping(topping) { this.pizza.toppings.push(topping); return this; } build() { return this.pizza; } } // 使用建造者 const myPizza = new PizzaBuilder(12) .setCrust(‘thin’) .addTopping(‘mushrooms’) .addTopping(‘peppers’) .addTopping(‘extra cheese’) .build(); myPizza.describe();

优点

  1. 封装性好:构建过程被封装在PizzaBuilder中,客户端代码简洁。
  2. 构建过程可控:可以精细控制每一步,也可以提供不同的建造者来构建不同风格的对象(如SpicyPizzaBuilder)。
  3. 解耦:将构建逻辑与产品本身分离,产品类Pizza保持稳定。

5. 高级与特殊用途模式

有些模式是为了解决JavaScript特有的一些问题,或者在一些非常特定的场景下能发挥巨大威力。

5.1 原型模式(基于对象的)

我们之前讨论的原型模式是基于函数的。还有一种更直接的方式,是直接以一个对象为原型,创建新对象。

// 原型对象 const vehiclePrototype = { init: function(make, model) { this.make = make; this.model = model; }, getDetails: function() { return `${this.make} ${this.model}`; } }; // 基于原型创建新对象 const car1 = Object.create(vehiclePrototype); car1.init(‘Toyota’, ‘Camry’); console.log(car1.getDetails()); // Toyota Camry const car2 = Object.create(vehiclePrototype); car2.init(‘Honda’, ‘Accord’); console.log(car2.getDetails()); // Honda Accord console.log(car1.getDetails === car2.getDetails); // true,方法共享

Object.create(proto)是ES5提供的方法,它创建一个新对象,并将其[[Prototype]](即__proto__)指向传入的proto对象。这种方式比new更加灵活和直观,特别是在你不想要“类”的概念,只想进行纯粹的原型继承时。

5.2 寄生构造函数模式

这种模式的基本思想是:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。这个函数看起来像构造函数(使用了new),但实际工作方式像工厂函数。

function SpecialArray() { // 创建数组 const values = new Array(); // 添加初始值(寄生过程) values.push.apply(values, arguments); // 添加自定义方法 values.toPipedString = function() { return this.join(‘|’); }; // 返回这个数组 return values; } const colors = new SpecialArray(‘red’, ‘blue’, ‘green’); console.log(colors.toPipedString()); // “red|blue|green” console.log(colors instanceof SpecialArray); // false!这是一个大坑 console.log(colors instanceof Array); // true

重要警告:如示例所示,通过寄生构造函数模式返回的对象与构造函数的原型链没有关系instanceof操作符无法得到预期结果。因此,除非在非常特殊的情况下(例如想为一个已有类型添加额外功能但又不想直接修改其原型),否则不推荐使用此模式

5.3 稳妥构造函数模式

由道格拉斯·克罗克福德提出,旨在创建“稳妥对象”(durable objects),即没有公共属性,其方法也不引用this的对象。这提供了一种极其严格的数据封装和隐私保护。

function Person(name, age, job) { // 创建要返回的对象 const o = new Object(); // 可以在这里定义私有变量和函数 const privateSecret = ‘This is secret’; // 定义公共方法(特权方法) o.sayName = function() { // 方法可以访问私有变量,但不使用this访问实例数据 console.log(name); // 直接使用参数,形成了闭包 console.log(privateSecret); }; o.getAge = function() { return age; }; // 返回对象 return o; } const friend = Person(‘Nicholas’, 29, ‘Software Engineer’); // 注意:没有使用 new friend.sayName(); // “Nicholas” console.log(friend.name); // undefined console.log(friend.privateSecret); // undefined console.log(friend.getAge()); // 29

特点

  1. 不使用new操作符调用构造函数。
  2. 不引用this
  3. 不使用new创建实例对象。
  4. 实例方法通过闭包访问传入的原始数据,外界无法直接访问。适用场景:在一些对安全要求极高的环境,或者某些框架内部,需要确保对象状态绝对不被外部篡改时。但因其写法特殊且性能有开销(每个实例方法都是独立的函数),日常开发中较少使用。

6. 现代JavaScript中的模式演进

ES6引入了classmodule等语法糖,以及SymbolWeakMap等新特性,使得一些传统模式的实现方式发生了改变,也催生了一些新的最佳实践。

6.1 ES6 Class 语法糖与模式结合

ES6的class本质是构造函数和原型模式的语法糖,但它让代码更清晰,更接近传统OOP语言。

// 使用Class实现“构造函数+原型”模式 class Animal { constructor(name) { this.name = name; } // 方法自动添加到 Animal.prototype speak() { console.log(`${this.name} makes a noise.`); } } class Dog extends Animal { constructor(name, breed) { super(name); // 调用父类构造函数 this.breed = breed; } speak() { // 方法重写 console.log(`${this.name} barks.`); } static describe() { // 静态方法 console.log(‘Dogs are great!’); } }

class语法让继承、静态方法等概念更加直观。我们可以轻松地将它与工厂模式结合:

class UserFactory { static createUser(type, name) { switch (type) { case ‘admin’: return new AdminUser(name); case ‘customer’: return new CustomerUser(name); default: throw new Error(‘Invalid user type’); } } }

6.2 模块模式与ES6 Modules

模块模式在ES5时代通过IIFE(立即调用函数表达式)和闭包实现,用于创建私有变量和公共API。

// ES5 模块模式 const myModule = (function() { let privateCounter = 0; // 私有变量 function privateMethod() { return ‘private’; } return { // 公共API increment: function() { privateCounter++; }, getValue: function() { return privateCounter; }, publicMethod: function() { return ‘public’ + privateMethod(); } }; })();

ES6引入了官方的模块系统,通过exportimport关键字,使得模块模式成为语言的一部分,更加标准化和强大。

// counter.js let privateCounter = 0; // 模块内的变量默认是“模块作用域”,对外不可见 export function increment() { privateCounter++; } export function getValue() { return privateCounter; } // 或者使用默认导出 export default { increment, getValue }; // main.js import { increment, getValue } from ‘./counter.js’; increment(); console.log(getValue()); // 1 // console.log(privateCounter); // 报错,无法访问

现代最佳实践:对于组织代码和封装,应优先使用ES6 Modules。对于需要在单个文件内创建具有私有状态的单例或复杂对象,模块模式(IIFE)依然有用。

6.3 使用Symbol和WeakMap实现真正的私有属性

ES6之前,模拟私有属性通常通过闭包或命名约定(如_privateVar)。ES6的SymbolWeakMap提供了更优雅的方案。

// 使用Symbol const _size = Symbol(‘size’); const _items = Symbol(‘items’); class Stack { constructor() { this[_items] = []; // “伪”私有,通过Object.getOwnPropertySymbols仍可访问 this[_size] = 0; } push(item) { this[_items].push(item); this[_size]++; } } // 使用WeakMap(更接近真正的私有) const privateData = new WeakMap(); class Person { constructor(name, age) { // 将实例作为键,私有数据作为值,存储在WeakMap中 privateData.set(this, { name, age }); } getName() { return privateData.get(this).name; } } const john = new Person(‘John’, 30); console.log(john.name); // undefined console.log(john.getName()); // ‘John’ // 当john实例被垃圾回收时,WeakMap中的对应条目也会被自动清除,避免内存泄漏

WeakMap方案是目前实现真正私有成员的最佳选择之一,尤其适合在类库或框架开发中。

7. 模式选择指南与实战避坑

了解了这么多模式,在实际项目中该如何选择呢?这里没有一个放之四海而皆准的答案,但可以根据一些核心原则和常见场景来决策。

7.1 模式选择决策树

面对一个对象创建需求时,可以按以下思路思考:

  1. 对象是否唯一?是 ->单例模式
  2. 对象的创建过程是否复杂(多步骤、多配置)?是 ->建造者模式
  3. 是否需要创建一系列相关对象(产品族)?是 ->抽象工厂模式
  4. 是否需要根据条件创建不同类型对象,且这些对象属于同一继承体系?是 ->工厂方法模式
  5. 是否只是简单创建几个类似实例?是 ->构造函数+原型模式ES6 Class
  6. 是否需要极强的封装和隐私保护?是 ->稳妥构造函数模式WeakMap私有字段
  7. 是否只是创建一次性的配置或命名空间?是 ->对象字面量

7.2 常见“坑”与解决方案实录

坑1:忘记使用new操作符

function Person(name) { this.name = name; } const p1 = Person(‘Alice’); // 错误!this指向了全局(或undefined in strict mode) console.log(window.name); // ‘Alice’ (非严格模式)

解决方案

  • 在构造函数内部使用new.target进行检查(ES6)。
  • 使用class语法,直接调用类会报错。
  • 工厂函数化:如果不希望使用者必须用new,可以设计成工厂函数。

坑2:原型继承导致引用类型属性共享

function Parent() { this.colors = [‘red’, ‘blue’]; } function Child() {} Child.prototype = new Parent(); // 原型继承 const c1 = new Child(); c1.colors.push(‘green’); const c2 = new Child(); console.log(c2.colors); // [‘red’, ‘blue’, ‘green’] // c1修改影响了c2!

解决方案:使用“构造函数继承+原型继承”的组合继承,或者ES6的class extends

坑3:过度设计,简单问题复杂化这是最常见的反模式。比如,仅仅为了创建两个配置不同的用户对象,就引入一个完整的抽象工厂。

实操心得KISS原则(Keep It Simple, Stupid)永远优先。在项目早期或对象结构简单时,优先使用最简单的对象字面量或构造函数。当重复代码出现、创建逻辑变得复杂时,再考虑引入设计模式进行重构。记住,模式是工具,不是目标。

坑4:混淆类模式与原型模式的思想试图用基于类的思维(如Java)来硬套JavaScript的原型链,导致对instanceofprototype的理解出现偏差。JavaScript的继承是原型链委托,不是类的复制。解决方案:多画原型链图,理解__proto__prototype的区别与联系。一个简单的记忆口诀:实例的__proto__指向其构造函数的prototype

7.3 性能考量与内存优化

  • 方法放在原型上:这是铁律。除非有特殊理由(如每个实例需要不同的方法实现),否则永远将方法定义在prototype上,避免成千上万个实例创建成千上万个函数副本。
  • 谨慎使用闭包:稳妥构造函数模式或工厂函数中,通过闭包创建的“私有变量”会导致每个实例都持有独立的作用域链,内存开销比使用原型方法大。在需要创建大量实例时需权衡。
  • Object.create()vsnewObject.create()更灵活,但new操作经过引擎深度优化,在纯粹创建实例的场景下性能通常更好。选择哪个应基于语义需求,而非细微性能差异。
  • 单例的惰性初始化:如果单例的创建成本很高,务必使用惰性初始化(即第一次调用getInstance时才创建),避免应用启动时的性能瓶颈。

对象创建模式是JavaScript高级编程的基石之一。从简单的{}到复杂的抽象工厂,每一种模式都是前辈开发者们针对特定问题总结出的最佳实践。理解它们,不仅能让你在面试中游刃有余,更能让你在面对真实项目中的复杂对象管理问题时,拥有一个清晰的工具箱,能够选出最合适的那把“锤子”。最好的学习方式,就是在理解原理后,在自己的项目中寻找可以应用这些模式的地方,亲手实践、重构,感受它们带来的代码结构上的提升。记住,没有最好的模式,只有最合适的场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 14:26:56

scrcpy 免费投屏指南:3 步在电脑上控制 Android 手机

scrcpy 免费投屏指南:3 步在电脑上控制 Android 手机 【免费下载链接】scrcpy Display and control your Android device 项目地址: https://gitcode.com/GitHub_Trending/sc/scrcpy scrcpy 是免费的开源投屏工具,把 USB 或 Wi-Fi 连接的 Android…

作者头像 李华
网站建设 2026/8/29 14:26:36

DEAP数据集情绪识别实战:脑电信号预处理到SVM分类全流程

简介:情绪识别是情感计算与脑机接口领域的核心议题,其目标是通过分析脑电信号等生理数据推断人的情感状态,在人机交互、心理健康监测、智能推荐等场景中具有重要价值。该任务的基本原理在于,不同情绪状态会引发脑电信号在频段能量…

作者头像 李华
网站建设 2026/8/29 14:24:52

大数模幂运算:从基础原理到RSA与区块链的高效实现

1. 项目概述:为什么大数模幂运算如此关键? 在密码学、区块链、安全协议这些领域里混久了,你一定会反复遇到一个看似简单、实则暗藏玄机的计算:给你一个巨大的底数 a ,一个同样巨大的指数 e ,还有一个巨…

作者头像 李华
网站建设 2026/8/29 14:24:41

从分钟级到秒级:uv 加速 Python 依赖管理实战指南

从分钟级到秒级:uv 加速 Python 依赖管理实战指南 【免费下载链接】uv An extremely fast Python package and project manager, written in Rust. 项目地址: https://gitcode.com/GitHub_Trending/uv/uv 给项目加一个新依赖,pip install 要转上两…

作者头像 李华
网站建设 2026/8/29 14:24:25

IIS3DHHC高稳定性加速度计:从噪声分析到倾角测量工程实践

1. 先把场景说清楚:不是所有加速度计都配叫“高稳定性”做倾角监测或者平台姿态检测的人,迟早会遇到一个尴尬问题:设备明明纹丝不动,打印出来的角度却在小数点后一位甚至两位来回跳。换过电源、换过滤波算法、拧紧过螺丝&#xff…

作者头像 李华
网站建设 2026/8/29 14:24:17

Taste-Skill 完整指南:给 AI 装上前端审美

Taste-Skill 完整指南:给 AI 装上前端审美 【免费下载链接】taste-skill Taste-Skill - gives your AI good taste. stops the AI from generating boring, generic slop 项目地址: https://gitcode.com/GitHub_Trending/ta/taste-skill Taste-Skill 是一套…

作者头像 李华