JavaScript对象创建模式:从工厂、构造函数到原型与组合模式
1. 从“面条代码”到模块化为什么我们需要封装如果你写过一段时间的JavaScript尤其是接手过一些“祖传”代码大概率见过这样的场景几百行代码挤在一个文件里变量名是a、b、c函数之间互相调用改一个地方可能引发三个未知的错误。这种代码通常被称为“面条代码”Spaghetti Code其可读性、可维护性和复用性几乎为零。封装就是解决这个问题的第一把钥匙。它不是什么高深莫测的玄学而是一种最朴素的编程思想将数据和操作数据的方法捆绑在一起对外隐藏内部复杂的实现细节只暴露必要的接口。想象一下你用的电视机你不需要知道里面复杂的电路板如何工作只需要知道按哪个按钮能开机、换台、调音量。电视机内部电路对你而言就是“封装”起来的。在JavaScript这个灵活到有些“松散”的语言里封装尤为重要。因为它没有Java、C#那种语言级别的class和private关键字ES6的class本质是语法糖#私有字段是后来才加入的早期的封装全靠开发者的“约定”和“模式”来实现。这就催生了工厂模式、构造函数模式、原型模式这些经典的创建对象模式。它们不仅仅是创建对象的不同写法更体现了JavaScript面向对象编程思想的演进脉络——从模仿到形成自己的特色。很多人学这些模式只记住了代码怎么写却忽略了它们各自要解决的核心问题以及背后的权衡。今天我们就抛开教科书式的定义从“为什么要用”和“实际怎么用”的角度把这三种模式的封装掰开揉碎了讲清楚并附上可以直接“抄作业”的代码实现和避坑指南。2. 工厂模式快速批量的对象“作坊”工厂模式可以理解为最直观的一种封装方式。它的核心思想很简单定义一个函数工厂这个函数负责接收原料参数按照固定的流程逻辑生产出产品对象然后返回这个产品。2.1 核心动机告别重复的new Object()在工厂模式出现之前如果我们想创建多个结构相似的对象可能会这样写const person1 { name: 小明, age: 20, sayName: function() { console.log(this.name); } }; const person2 { name: 小红, age: 22, sayName: function() { console.log(this.name); } };这带来了两个明显问题1. 代码重复每个对象都要写一遍属性和方法2.sayName方法在每个对象里都存了一份浪费内存。工厂模式就是为了解决这种重复劳动而生的。2.2 标准实现与代码解析一个典型的工厂函数如下所示function createPerson(name, age) { // 1. 创建一个新对象准备原材料 const obj new Object(); // 2. 为这个对象添加属性和方法加工组装 obj.name name; obj.age age; obj.sayName function() { console.log(this.name); }; // 3. 返回加工好的对象出厂 return obj; } // 使用工厂 const person1 createPerson(小明, 20); const person2 createPerson(小红, 22); person1.sayName(); // 输出小明 console.log(person1 instanceof Object); // true console.log(person1.constructor); // [Function: Object]逐行拆解与思考const obj new Object();这是最基础的对象创建方式。在函数内部创建对象将创建细节封装了起来。属性和方法的赋值将传入的参数绑定到对象上。注意这里每个对象的sayName方法都是全新的函数即使它们功能一模一样。return obj;这是工厂模式的关键。通过返回值将创建好的对象交给调用者。2.3 工厂模式的优缺点与适用场景优点简单直观逻辑清晰容易理解和实现。解耦将对象的创建与使用分离。调用者无需关心对象是如何被组装的只需知道“工厂”能生产什么。灵活性可以在工厂函数内部根据参数进行复杂的逻辑判断返回不同类型的对象。致命缺点对象类型识别问题这是工厂模式最大的痛点。所有通过工厂创建的对象其构造函数都指向顶层的Object而不是createPerson。person1 instanceof createPerson会返回false。这在需要精确判断对象来源的场景下比如插件系统、错误追踪非常不便。内存浪费如前所述每个对象都拥有自己独立的方法副本。创建100个对象就有100个功能相同的sayName函数在内存中这是极大的资源浪费。适用场景当你需要快速创建大量简单、轻量、且不需要复杂类型识别和继承关系的对象时。适用于一些工具类对象的创建其生命周期短且更关注数据本身而非行为。作为学习理解封装概念的入门范例非常合适但在现代稍具规模的JavaScript项目中单纯使用工厂模式的情况已经比较少了。注意虽然工厂模式有缺点但它体现的“封装创建过程”的思想被广泛用于更高阶的设计模式中如抽象工厂、建造者模式。理解它是理解更复杂模式的基础。3. 构造函数模式赋予对象“身份证”为了解决工厂模式“对象类型无法识别”的问题JavaScript提供了构造函数模式。这不仅仅是语法上的变化更是一种理念的升级让创建的对象知道自己“是谁生的”。3.1new操作符背后的四步魔法构造函数本质上就是一个普通函数但通常约定函数名首字母大写。它的魔力来自于new操作符。当你使用new Constructor()时后台默默做了四件事创建一个全新的空对象。将这个新对象的内部[[Prototype]]即__proto__链接到构造函数的prototype对象。这是实现继承的关键一步。将构造函数内部的this绑定到这个新创建的对象。如果构造函数没有显式返回一个对象则自动返回这个新创建的对象。理解了这四步就能看透构造函数模式的本质。3.2 标准实现与内存问题剖析让我们用构造函数重写上面的例子function Person(name, age) { // 此时的 this 指向 new 创建的新对象 this.name name; this.age age; this.sayName function() { console.log(this.name); }; // 没有 return 语句引擎会自动返回 this } const person1 new Person(小明, 20); const person2 new Person(小红, 22); person1.sayName(); // 输出小明 console.log(person1 instanceof Person); // true问题解决了 console.log(person1.constructor); // [Function: Person]关键进步现在person1 instanceof Person返回true我们可以明确知道person1是由Person“构造”出来的。对象的“身份证”问题解决了。遗留的顽疾方法的内存浪费然而喜悦是短暂的。我们仔细看this.sayName function() { ... }这一行依然在每次调用new Person()时都会创建一个全新的函数对象并赋值给新实例的sayName属性。person1.sayName person2.sayName的结果是false。内存浪费的问题和工厂模式一模一样甚至更糟因为它给每个实例都打上了“我是Person”的标签但内部却做着重复存储的事。3.3 进阶尝试将方法移到构造函数外部一个直观的优化思路是把函数定义移到构造函数外面构造函数内部只进行引用。function sayName() { console.log(this.name); } function Person(name, age) { this.name name; this.age age; this.sayName sayName; // 引用外部函数 } const person1 new Person(小明, 20); const person2 new Person(小红, 22); console.log(person1.sayName person2.sayName); // true内存问题解决这样做确实解决了内存问题所有实例共享同一个sayName函数。但它带来了新的问题污染全局命名空间sayName函数暴露在全局可能与其他代码冲突。封装性被破坏从代码组织上看sayName本应是Person的“私有”行为现在却独立在外破坏了对象的封装性。如果有很多方法全局就会有很多函数难以管理。构造函数模式解决了类型识别但优雅地解决共享方法的问题需要引出更强大的机制——原型。4. 原型模式共享的“家族宝藏”JavaScript中每个函数都有一个特殊的属性prototype原型属性。它是一个对象。当使用new调用构造函数创建实例时该实例的内部[[Prototype]]会指向构造函数的prototype对象。原型模式的核心就是利用这个链接让所有实例共享原型对象上的属性和方法。4.1 理解原型链属性的查找机制当你访问一个对象的属性比如person1.sayName时JavaScript引擎会执行以下搜索首先在对象实例本身查找是否有该属性。有则返回搜索停止。如果没有则沿着实例的__proto__指针到它的原型对象即Person.prototype上查找。如果原型对象上还没有则继续沿着原型对象的__proto__向上查找原型对象也是对象它也有原型直到找到Object.prototype如果还没有则返回undefined。这条搜索路径就是原型链。原型模式正是基于这个机制。4.2 标准实现将方法定义在原型上让我们用原型模式彻底改造Person// 1. 定义构造函数只初始化实例独有的属性 function Person(name, age) { this.name name; // 实例属性 this.age age; // 实例属性 } // 2. 将共享的方法添加到构造函数的 prototype 对象上 Person.prototype.sayName function() { console.log(this.name); }; // 3. 还可以添加共享的属性需谨慎 Person.prototype.species Homo sapiens; const person1 new Person(小明, 20); const person2 new Person(小红, 22); person1.sayName(); // 输出小明 console.log(person1.sayName person2.sayName); // true完美共享 console.log(person1.species); // Homo sapiens console.log(person2.species); // Homo sapiens优势分析极致的内存效率sayName方法只在内存中存在一份所有Person实例通过原型链共享它。强大的封装性方法被很好地组织在Person.prototype这个与构造函数关联的对象下没有污染全局空间。保留类型识别instanceof和constructor判断依然有效。动态性即使在创建实例之后我们修改Person.prototype所有已存在的实例也能立即“看到”这个变化因为查找是动态的。4.3 原型模式的陷阱与最佳实践原型模式非常强大但使用不当也会踩坑。陷阱一重写原型对象会切断已有实例的联系function Person() {} const person1 new Person(); // 最初的原型上有一个方法 Person.prototype.sayHi function() { console.log(Hi); }; person1.sayHi(); // 输出Hi // 错误做法完全重写 prototype 对象 Person.prototype { sayHello: function() { console.log(Hello); } }; const person2 new Person(); person2.sayHello(); // 输出Hello person2.sayHi(); // 报错person2.sayHi is not a function person1.sayHi(); // 输出Hi person1依然链接到旧的原型 person1.sayHello(); // 报错person1.sayHello is not a functionperson1的[[Prototype]]仍然指向最初的那个原型对象。重写Person.prototype只是让构造函数指向了一个新的原型对象不影响已创建的实例。新创建的person2则指向新的原型对象。这导致了混乱。最佳实践永远通过Constructor.prototype.methodName ...的形式来添加属性或方法避免直接用一个新对象覆盖prototype。如果非要覆盖必须在创建任何实例之前完成。陷阱二原型上的引用类型属性会被所有实例共享这是原型模式最著名的“坑”。function Person() {} Person.prototype.friends [Alice, Bob]; // 引用类型属性 Person.prototype.sayFriends function() { console.log(this.friends); }; const person1 new Person(); const person2 new Person(); person1.friends.push(Charlie); console.log(person1.friends); // [Alice, Bob, Charlie] console.log(person2.friends); // [Alice, Bob, Charlie]person2的也被改了因为person1.friends和person2.friends指向的是同一个数组原型上的那个。修改其中一个必然影响另一个。最佳实践将需要独立维护的属性尤其是引用类型定义在构造函数内部作为实例属性。只有纯函数、常量或真正需要共享的配置项才适合放在原型上。 修正后的写法function Person() { this.friends [Alice, Bob]; // 每个实例拥有独立的数组 } Person.prototype.sayFriends function() { console.log(this.friends); };5. 组合模式实践中最常用的“黄金法则”经过前面的分析我们发现没有一种模式是完美的工厂模式类型识别难内存浪费。构造函数模式解决了类型但没解决内存。原型模式解决了内存和类型但共享引用属性是坑。于是在实践中一个结合了构造函数模式和原型模式优点的“组合模式”成为了最广泛使用的默认选择。其规则非常简单使用构造函数模式定义实例属性使用原型模式定义共享的方法和常量属性。5.1 标准组合模式实现这就是目前最经典、最推荐的JavaScript对象创建模式。// 1. 构造函数定义每个实例独有的属性 function Person(name, age, job) { this.name name; this.age age; this.job job; this.friends [Alice, Bob]; // 引用类型也放在这里每个实例独立 } // 2. 原型定义所有实例共享的方法 Person.prototype { constructor: Person, // 显式指回构造函数保持完整性 sayName: function() { console.log(this.name); }, sayJob: function() { console.log(My job is ${this.job}.); } }; // 使用 const person1 new Person(Nicholas, 29, Software Engineer); const person2 new Person(Greg, 27, Doctor); person1.friends.push(Van); console.log(person1.friends); // [Alice, Bob, Van] console.log(person2.friends); // [Alice, Bob] 互不影响 console.log(person1.sayName person2.sayName); // true 共享方法 console.log(person1 instanceof Person); // true console.log(person1.constructor Person); // true5.2 为什么这是“黄金法则”内存高效方法只在原型上存在一份无论创建多少实例。实例独立每个实例拥有自己的一份属性副本特别是引用类型属性互不干扰。类型清晰instanceof和constructor都能正确工作。封装良好属性和方法被清晰地组织在构造函数和原型中代码结构一目了然。这种模式是如此成功以至于ECMAScript 5的Object.create()和ECMAScript 6的class语法其底层思想都与之高度一致。ES6的class可以看作是这种组合模式的语法糖它让代码看起来更接近传统面向对象语言但本质没变。5.3 从组合模式到ES6 Class理解了组合模式再看ES6的class就豁然开朗了。class Person { // 构造函数对应之前的构造函数模式部分 constructor(name, age, job) { this.name name; this.age age; this.job job; this.friends [Alice, Bob]; } // 类方法对应之前的原型模式部分 sayName() { console.log(this.name); } sayJob() { console.log(My job is ${this.job}.); } } // 使用完全一样 const person1 new Person(Nicholas, 29, Software Engineer);class中的constructor就是原来的构造函数class中定义的方法会自动挂载到原型上。它没有引入新的面向对象模型只是让组合模式的写法更优雅、更不易出错比如自动处理prototype.constructor的指向。6. 封装模式的演进与选择指南回顾这三种模式其实是一条清晰的演进路径工厂模式解决了“创建对象”的封装问题但对象没有“根”。构造函数模式通过new和this赋予了对象“身份”类型识别但共享能力不足。原型模式通过原型链实现了属性和方法的完美共享但需要警惕引用共享的陷阱。组合模式构造函数原型取二者之长成为事实标准。ES6 Class组合模式的语法糖提供更现代、更安全的书写方式。在实际项目中的选择建议对于现代项目ES6毫不犹豫地使用class。它清晰、安全、符合潮流是官方推荐的语法。Babel等工具会帮你处理好兼容性问题。对于需要兼容极老环境或学习底层原理深入理解组合模式。它是class的基石能帮你透彻理解this、原型链、new等核心概念。工厂模式在需要根据复杂条件创建不同类型对象、或不想暴露构造函数时仍有其用武之地。例如一个创建不同UI组件Button, Input, Modal的函数可以根据传入的type返回不同的对象调用者无需关心具体的构造函数。纯原型模式单独使用的情况较少通常在与Object.create()结合进行原型式继承时会出现。理解这些模式最终目的不是为了在写代码时纠结用哪一个而是为了在阅读任何JavaScript代码无论是古老的库还是现代的框架时都能一眼看穿其对象组织的套路在遇到诡异bug时比如为什么修改了这个数组另一个对象也变了能迅速定位到是否是原型共享引用类型惹的祸。这才是封装思想带给我们的真正价值——写出更清晰、更健壮、更易于协作的代码。

相关新闻

前端校招面经:字节阿里腾讯美团四厂面试全记录

前端校招面经:字节阿里腾讯美团四厂面试全记录

说实话,现在回头看2021届的校招,依然觉得那是一段很特别的日子。作为前端方向的学生,我在2020年从春招实习一路面到秋招,陆续投了字节跳动、阿里巴巴、腾讯、美团这四家,基本把国内主流大厂的前端面试风格都感受了一遍…

2026/8/29 21:14:34 阅读更多 →
阿里前端暑期实习面试复盘:从八股文到项目深挖的实战指南

阿里前端暑期实习面试复盘:从八股文到项目深挖的实战指南

开篇:一次把“八股文”问成“应用题”的面试我拿到阿里巴巴前端暑期实习offer已经是去年的事了,但这段时间陆续有学弟学妹来问我“阿里面试到底问什么”“前端暑期实习要准备到什么程度”,所以我决定把整个面试过程完整复盘一遍。先说结论&am…

2026/8/29 21:13:33 阅读更多 →
手动模拟大数乘法:从算法原理到Python实现详解

手动模拟大数乘法:从算法原理到Python实现详解

1. 从“算不过来”到“手动模拟”:为什么大数乘法是程序员的必修课 你肯定遇到过这种情况:写个简单的计算器,用户输入两个很大的整数,比如 12345678901234567890 乘以 98765432109876543210 ,程序直接给你返回一个…

2026/8/29 21:13:33 阅读更多 →

最新新闻

自助图文打印系统:UI/PHP/教程三位一体解决方案

自助图文打印系统:UI/PHP/教程三位一体解决方案

简介:自助打印系统是面向图文快印场景的轻量级数字化基础设施,其核心在于解决用户端操作断点与后端文件处理可靠性之间的协同问题。原理上融合微信原生小程序UI交互规范、PHP驱动的ImageMagickGhostscript文件流水线、以及覆盖硬件校准与支付补单的工程化…

2026/8/29 21:52:11 阅读更多 →
蓝桥杯国赛B组算法实战复盘:从日期计算到数位DP的解题策略

蓝桥杯国赛B组算法实战复盘:从日期计算到数位DP的解题策略

1. 从“国赛B组”说起:一次算法竞赛的实战复盘最近整理硬盘,翻到了2021年参加蓝桥杯国赛的代码和笔记。那一年,C/C大学B组的题目,现在回头看,依然能感受到赛场上的那种紧张和烧脑。很多朋友,尤其是正在备赛…

2026/8/29 21:52:11 阅读更多 →
游戏开发实习生笔试指南:C++、算法与渲染网络全考点

游戏开发实习生笔试指南:C++、算法与渲染网络全考点

去年5月26号下午,我坐在畅游的笔试教室里,拿到那套游戏开发实习生的题目时,第一反应是:这套题不像网上流传的那些“大厂刷题集”,反而很像一个主程随手给你出的验收卷。整套卷子覆盖C、算法、基础图形学、物理和一点网…

2026/8/29 21:52:11 阅读更多 →
SO-101机械臂macOS原生驱动:绕过ROS2构建跨平台实时控制链

SO-101机械臂macOS原生驱动:绕过ROS2构建跨平台实时控制链

简介:机械臂运动控制本质上是硬件时序、操作系统调度与中间件通信的协同问题。当面对微秒级CAN帧校验、macOS kqueue事件模型及Metal渲染等硬约束时,传统ROS2架构因依赖Linux epoll、DDS网络栈和Gazebo仿真器而失效。SO-101在macOS上的稳定运行&#xff…

2026/8/29 21:51:09 阅读更多 →
前端面试八股文进阶:从背题到构建知识体系

前端面试八股文进阶:从背题到构建知识体系

最近又把coderwhy的深入前端就业指导八股文这套内容从头到尾翻了一遍。作为一个在前端圈子摸爬滚打了七八年、既当过面试官也被面试官虐过的人,我最初看到"八股文"这三个字是有点抵触的——毕竟谁没被"JS事件循环讲一下"这种问题支配过呢&#…

2026/8/29 21:51:09 阅读更多 →
美赛LaTeX写作指南:从环境搭建到高效协作的完整工作流

美赛LaTeX写作指南:从环境搭建到高效协作的完整工作流

1. 项目概述:为什么美赛论文必须用LaTeX?如果你正在准备美国大学生数学建模竞赛(M赛),并且还在纠结是用Word还是LaTeX来写论文,那我以一个过来人的身份告诉你:直接上LaTeX,别犹豫。这…

2026/8/29 21:51:09 阅读更多 →

日新闻

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

一、设置环境变量etc目录下的profile.d文件目录 /etc/profile.d1、编写 vi test.sh文件内容# jdk变量 export ZHK_HOME/root export PATH$PATH:$ZHK_HOME/test # 可以取出来ZHK_HOME变量给ZZZ_HOME赋值 export ZZZ_HOME${ZHK_HOME}/test2、刷新 执行source /etc/profile 命令使…

2026/8/29 0:00:24 阅读更多 →
【JavaScript】内存管理-垃圾回收机制-内存泄露

【JavaScript】内存管理-垃圾回收机制-内存泄露

内存管理 C 语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。 而 JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。释放的过程称为垃圾回收。 整…

2026/8/29 0:00:24 阅读更多 →
Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP 的目标是把 MCP(Model Context Protocol)能力延伸到真实嵌入式硬件实验室:AI Agent 通过一个标准化的 MCP Server,就能查看目标板状态、控制上电断电、复位开发板、读取串口日志,甚至执行镜像刷写。对于经…

2026/8/29 0:00:24 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/29 18:08:35 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/28 23:05:07 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/28 19:47:53 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/29 4:34:53 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/28 17:43:04 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/29 2:05:18 阅读更多 →