原型模式核心概念
- 原型模式 (Prototype Pattern):
- 通过复制 (克隆) 已有对象 (原型) 来创建新对象,而非通过构造函数重新初始化;
- 这种方式可以避免重复创建相似对象的开销,尤其适合创建复杂对象、对象初始化成本高的场景;
- 类图:
- 实现代码:
/** * 1. 抽象原型接口(定义克隆规范) */ interface Prototype { clone(): Prototype; } /** * 2. 具体原型类(核心:clone 方法不调用构造函数,直接复制原型) */ class UserPrototype implements Prototype { public name: string; public age: number; public preferences: { theme: string; fontSize: number }; /** * 仅用于创建「原型对象」的构造函数(只执行一次) * @param name 用户名 * @param age 年龄 * @param preferences 偏好设置 */ constructor(name: string, age: number, preferences: { theme: string; fontSize: number }) { this.name = name; this.age = age; this.preferences = preferences; console.log("构造函数仅执行一次(原型对象创建时)"); } /** * 克隆方法(核心改进:不调用 new UserPrototype(),直接复制原型) * 用 Object.create 继承原型的属性,再深拷贝引用类型,避免共享 * @returns 新的 UserPrototype 实例 */ clone(): UserPrototype { // 步骤1:创建一个空对象,原型指向当前实例(继承所有属性) const clone = Object.create(this); // 步骤2:深拷贝引用类型属性(避免和原型共享) clone.preferences = { ...this.preferences }; // 步骤3:返回克隆对象(全程未调用构造函数) return clone; } setPreferences(preferences: { theme: string; fontSize: number }) { this.preferences = preferences; } } // ---------------------- 测试代码 ---------------------- // 1. 创建原型对象(仅这一步调用构造函数) const userPrototype = new UserPrototype("张三", 25, { theme: "dark", fontSize: 16 }); // 输出:构造函数仅执行一次(原型对象创建时) // 2. 克隆新对象(核心:不调用构造函数) const user1 = userPrototype.clone(); const user2 = userPrototype.clone(); // 控制台不会再输出构造函数的日志 → 证明未调用构造函数 // 3. 修改新实例的属性(验证深克隆:引用类型属性互不影响) user1.name = "李四"; user1.preferences.theme = "light"; user2.age = 30; user2.preferences.fontSize = 18; // 输出结果验证 console.log("原型实例:", userPrototype); // 原型实例: { name: '张三', age: 25, preferences: { theme: 'dark', fontSize: 16 } } console.log("克隆实例1:", user1); // 克隆实例1: { name: '李四', age: 25, preferences: { theme: 'light', fontSize: 16 } } console.log("克隆实例2:", user2); // 克隆实例2: { name: '张三', age: 30, preferences: { theme: 'dark', fontSize: 18 } } // 验证引用类型是否独立(深克隆生效) console.log(user1.preferences === userPrototype.preferences); // false
原型模式的常用场景
批量创建相似对象(如列表数据)
- 当需要生成大量结构相似的对象 (如表格数据、模拟数据) 时,克隆原型对象比重复 new 更高效:
// 列表项原型 const listItemPrototype = { id: 0, title: "", status: "pending", createTime: new Date() }; // 批量生成列表数据(克隆原型) function generateListData(count: number) { return Array.from({ length: count }, (_, index) => { const clonedItem = { ...listItemPrototype }; // 克隆原型 clonedItem.id = index + 1; clonedItem.title = `任务${index + 1}`; clonedItem.createTime = new Date(Date.now() - index * 86400000); return clonedItem; }); } // 生成 10 条列表数据 const listData = generateListData(10);
数据模拟
- canvas 数据生成,原型模式生成小球;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>原型模式 - 动态圆形</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } canvas { display: block; /* 消除默认边距 */ } </style> </head> <body> <canvas id="canvas" width="1000" height="300"></canvas> <script> // 封装成独立模块,避免全局变量污染 (function () { // ========== 工具函数(优化) ========== /** * 生成随机十六进制颜色(非递归版,性能更优) * @returns {string} 十六进制颜色值 */ const getRandomColor = () => { const color = Math.floor(Math.random() * 0xFFFFFF) .toString(16) .padStart(6, '0'); // 补零,避免递归 return `#${color}`; }; // ========== 原型模式核心:圆形原型类 ========== class CirclePrototype { /** * 原型构造函数(仅初始化一次原型属性) * @param {number} defaultRadius 默认半径 */ constructor(defaultRadius = 30) { this.defaultRadius = defaultRadius; this.x = 0; this.y = 0; this.radius = defaultRadius; } /** * 原型克隆方法(核心:创建新实例,复用原型逻辑) * @param {number} x 横坐标 * @param {number} y 纵坐标 * @returns {CirclePrototype} 克隆的新圆形实例 */ clone(x, y) { const circle = Object.create(this); // 继承原型方法/属性 circle.x = x; circle.y = y; circle.radius = this.defaultRadius; // 复用原型默认半径 return circle; } /** * 更新圆形状态(半径递减) * @returns {boolean} 是否需要继续渲染 */ update() { this.radius = Math.max(0, this.radius - 1); // 避免半径为负 return this.radius > 0; } /** * 渲染圆形(复用原型渲染逻辑) * @param {CanvasRenderingContext2D} ctx Canvas 上下文 */ render(ctx) { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI); ctx.fillStyle = getRandomColor(); ctx.fill(); ctx.closePath(); // 闭合路径,优化绘制性能 } } // ========== 初始化逻辑 ========== const init = () => { // 获取 Canvas 元素和上下文 const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return; // 1. 创建圆形原型对象(仅创建一次,后续都克隆它) const circlePrototype = new CirclePrototype(30); // 2. 存储活跃的圆形实例(替代原全局数组) let activeCircles = []; // 3. 鼠标移动事件:克隆原型生成新圆形 canvas.addEventListener('mousemove', (event) => { // 核心:通过克隆原型创建新圆形,而非 new Circle() const newCircle = circlePrototype.clone( event.clientX, event.clientY ); activeCircles.push(newCircle); }); // 4. 动画循环(用 requestAnimationFrame 替代 setInterval,更流畅) const animate = () => { // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 过滤并更新/渲染圆形(优化:一次遍历完成过滤+渲染) activeCircles = activeCircles.filter(circle => { const isAlive = circle.update(); if (isAlive) { circle.render(ctx); } return isAlive; }); requestAnimationFrame(animate); // 持续动画 }; // 启动动画 animate(); }; // 页面加载完成后初始化 window.addEventListener('DOMContentLoaded', init); })(); </script> </body> </html> - 演示:
*图表/表单配置复用
原型模式的优缺点
- 优点:
- 性能提升:避免重复执行复杂的初始化过程;
- 减少子类构造:无需创建大量的工厂类;
- 动态配置:可以在运行时添加和移除原型;
- 简化创建:隐藏了对象创建的复杂性;
- 缺点:
- 克隆复杂:需要正确处理循环引用和深拷贝;
- 继承问题:如果类已经有继承关系,实现克隆可能变得复杂;
- 接口设计:所有子类都必须实现克隆方法;
最佳实践建议
- 明确克隆类型:根据需求选择浅拷贝或深拷贝;
- 使用原型注册表:管理常用原型对象;
- 注意引用类型:确保克隆时正确处理数组、对象等引用类型;
- 实现 Cloneable 接口:使用 TypeScript 接口强制实现克隆方法;
- 考虑使用序列化:对于复杂对象,可以考虑使用 JSON 序列化实现深拷贝;
策略模式(行为型模式)
上一篇