讲解

复印一份填好的表格再改两处,比从空白表格重新填要快得多——这就是原型模式的直觉:创建新对象时,不走「从零构造」,而是拿一个现成的对象当「原型」,克隆出副本再微调。当对象的创建成本高(要查库、要解析配置),或者系统运行时需要大量「差不多但略有不同」的对象时,克隆是最经济的创建方式。

GoF 的定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。游戏里铺天盖地的怪物是教科书场景:每种怪配置一个原型实例,刷怪 = clone() 后改改坐标和血量。

实现克隆时绕不开的核心问题是深拷贝与浅拷贝。浅拷贝(对象展开 { ...obj }、Object.assign)只复制第一层,嵌套的对象和数组仍是共享引用——改了副本的嵌套字段,原型跟着变,这是克隆最常见的线上事故来源。深拷贝要递归复制整个对象图:现代 JavaScript 有内建的 structuredClone(),覆盖绝大多数数据型对象(不支持函数、DOM 节点、类实例的原型链);需要保留类身份(方法、私有字段)时则要自己实现 clone() 方法。

TypeScript 里实现原型模式有个优雅点:给类定义一个 clone(): ThisType 风格的接口(Cloneable { clone(): T }),每个可克隆类自己负责字段级复制,调用方完全不关心内部结构。这比依赖外部工具函数更内聚,也让「哪些字段要深拷贝」的决策留在最懂它的类里。

示例

对比浅拷贝的坑和 structuredClone 的正确姿势——批量克隆敌人时,嵌套的技能数组必须各自独立:

import assert from 'node:assert/strict';

interface Enemy {
  name: string;
  hp: number;
  skills: string[];
}

// 浅拷贝:只复制第一层,嵌套数组被共享
const slime: Enemy = { name: '史莱姆', hp: 30, skills: ['撞击'] };
const shallow: Enemy = { ...slime };
shallow.skills.push('毒液');
assert.equal(slime.skills.length, 2); // 原型也被污染了!

// 深拷贝:structuredClone 递归复制整个对象图
const base: Enemy = { name: '史莱姆', hp: 30, skills: ['撞击'] };
const copy1 = structuredClone(base);
const copy2 = structuredClone(base);

copy1.hp = 25; // 这只怪被打了一下
copy1.skills.push('毒液'); // 这只怪变异了

assert.equal(copy2.hp, 30);
assert.deepEqual(copy2.skills, ['撞击']);
assert.deepEqual(base.skills, ['撞击']); // 原型安然无恙

console.log('copy1:' + copy1.name + ' hp=' + copy1.hp + ' 技能=' + copy1.skills.join('/'));
console.log('copy2 不受影响:hp=' + copy2.hp + ' 技能=' + copy2.skills.join('/'));

断言把两种拷贝的差别钉死了:浅拷贝后 shallow.skills.push 直接改到了原型 slime 身上(length 变成 2);而 structuredClone 出的 copy1 怎么折腾,copy2 和原型 base 都保持原样。批量生成「同模板微调」的对象时,先用 structuredClone 复印、再改差异字段,是既快又安全的标准动作。

常见坑

  • 以为对象展开就是「复制」:{ ...obj } 是浅拷贝,嵌套结构共享引用;对象一有嵌套数组/对象就要想到这一层。
  • 用 JSON.parse(JSON.stringify()) 当深拷贝:会丢 undefined、函数、Date 变字符串、循环引用直接抛错;优先 structuredClone。
  • 克隆类实例丢原型:structuredClone 一个 class 实例得到的是普通对象,方法全没了;类实例要自己实现 clone() 方法。
  • 克隆共享可变状态却忘了隔离:原型模式的前提是克隆体能独立演化;如果某些字段本来就设计为共享(比如全局配置),要明确地浅拷它们,别一锅端。
  • 循环引用的自建深拷贝栈溢出:手写递归深拷贝必须处理循环引用(用 Map 记录已拷贝对象),structuredClone 内建支持。

小结

原型模式用克隆代替从零构造,适合创建成本高或批量「同模板微调」的对象;浅拷贝只复制第一层、深拷贝用 structuredClone,类实例需自备 clone()。创建型五模式到此讲完,下一章进入结构型:把不兼容的接口接到一起的适配器模式。