ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/8/29 14:30:10 拓冰建站 浏览量
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: [] };当只有两个用户时这没问题。但当你有成千上万个用户并且每个用户都需要新增address、vipLevel、loginHistory等属性甚至每个用户对象都需要一个addToCart方法时问题就来了。代码中会散落着大量重复的对象字面量修改一个公共方法比如修改addToCart的逻辑会成为一场灾难。对象创建模式的价值就在于它通过一套约定俗成的“模板”或“流程”将对象的创建过程规范化、抽象化。其核心设计哲学可以归纳为以下几点封装变化点将对象创建过程中可能变化的部分如对象的类型、初始配置、内部实现隔离出来避免其扩散到整个代码库。提高复用性创建逻辑被封装后可以在多个地方复用避免重复代码。解耦客户端与具体类使用对象的代码客户端不需要关心对象的具体类型和复杂的构建过程它只依赖于一个抽象的接口或工厂这使得替换对象类型变得非常容易。控制实例化过程有些模式如单例模式可以严格控制一个类只有一个实例有些模式如建造者模式可以精细控制一个复杂对象的构建步骤。理解这些哲学能帮助我们在面对具体问题时不是机械地套用模式而是真正理解为何选择它。接下来我们就进入正题看看这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();优点封装性好构建过程被封装在PizzaBuilder中客户端代码简洁。构建过程可控可以精细控制每一步也可以提供不同的建造者来构建不同风格的对象如SpicyPizzaBuilder。解耦将构建逻辑与产品本身分离产品类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特点不使用new操作符调用构造函数。不引用this。不使用new创建实例对象。实例方法通过闭包访问传入的原始数据外界无法直接访问。适用场景在一些对安全要求极高的环境或者某些框架内部需要确保对象状态绝对不被外部篡改时。但因其写法特殊且性能有开销每个实例方法都是独立的函数日常开发中较少使用。6. 现代JavaScript中的模式演进ES6引入了class、module等语法糖以及Symbol、WeakMap等新特性使得一些传统模式的实现方式发生了改变也催生了一些新的最佳实践。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引入了官方的模块系统通过export和import关键字使得模块模式成为语言的一部分更加标准化和强大。// 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的Symbol和WeakMap提供了更优雅的方案。// 使用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 模式选择决策树面对一个对象创建需求时可以按以下思路思考对象是否唯一是 -单例模式。对象的创建过程是否复杂多步骤、多配置是 -建造者模式。是否需要创建一系列相关对象产品族是 -抽象工厂模式。是否需要根据条件创建不同类型对象且这些对象属于同一继承体系是 -工厂方法模式。是否只是简单创建几个类似实例是 -构造函数原型模式或ES6 Class。是否需要极强的封装和隐私保护是 -稳妥构造函数模式或WeakMap私有字段。是否只是创建一次性的配置或命名空间是 -对象字面量。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的原型链导致对instanceof、prototype的理解出现偏差。JavaScript的继承是原型链委托不是类的复制。解决方案多画原型链图理解__proto__和prototype的区别与联系。一个简单的记忆口诀实例的__proto__指向其构造函数的prototype。7.3 性能考量与内存优化方法放在原型上这是铁律。除非有特殊理由如每个实例需要不同的方法实现否则永远将方法定义在prototype上避免成千上万个实例创建成千上万个函数副本。谨慎使用闭包稳妥构造函数模式或工厂函数中通过闭包创建的“私有变量”会导致每个实例都持有独立的作用域链内存开销比使用原型方法大。在需要创建大量实例时需权衡。Object.create()vsnewObject.create()更灵活但new操作经过引擎深度优化在纯粹创建实例的场景下性能通常更好。选择哪个应基于语义需求而非细微性能差异。单例的惰性初始化如果单例的创建成本很高务必使用惰性初始化即第一次调用getInstance时才创建避免应用启动时的性能瓶颈。对象创建模式是JavaScript高级编程的基石之一。从简单的{}到复杂的抽象工厂每一种模式都是前辈开发者们针对特定问题总结出的最佳实践。理解它们不仅能让你在面试中游刃有余更能让你在面对真实项目中的复杂对象管理问题时拥有一个清晰的工具箱能够选出最合适的那把“锤子”。最好的学习方式就是在理解原理后在自己的项目中寻找可以应用这些模式的地方亲手实践、重构感受它们带来的代码结构上的提升。记住没有最好的模式只有最合适的场景。