如果你曾经好奇 react 如何高效地更新界面,或者想知道为什么 React 性能这么好,那么这篇文章会给你答案。今天我们要聊的是 React 核心架构中一个重要的设计模式——原型模式。
先来看一个生活中的例子。假设你要制作一批相同的椅子,最直接的方法是每把椅子都从头开始制作。但更聪明的方法是先做一个完美的样品,然后基于这个样品快速复制。
在编程中,原型模式就是这个思路:创建一个模板对象,然后通过复制这个模板来创建新对象,而不是每次都重新构建。
想象你在开发一个棋盘游戏。棋盘有 64 个格子,每个格子都有颜色、位置和棋子信息。
最直接的写法是这样的:
class BoardSquare {
constructor(color, row, file, piece) {
this.color = color; // 颜色
this.row = row; // 行号
this.file = file; // 列号
this.piece = piece; // 棋子
}
occupySquare(piece) {
this.piece = piece;
}
clearSquare() {
this.piece = null;
}
}
// 创建64个格子
const squares = [];
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 8; j++) {
const color = (i + j) % 2 === 0 ? 'white' : 'black';
squares.push(new BoardSquare(color, i, j, null));
}
}这种方法有个问题:如果后来需要给格子添加新属性,比如背景图片或者特殊样式,就需要修改所有创建格子的代码。这很容易出错,而且效率不高。
使用原型模式,我们可以这样做:
class BoardSquarePrototype {
constructor(prototype) {
this.prototype = prototype;
}
clone() {
return Object.assign(new BoardSquare(), this.prototype);
}
}
// 创建模板
const whiteSquareTemplate = new BoardSquare('white', null, null, null);
const prototype = new BoardSquarePrototype(whiteSquareTemplate);
// 快速复制
const square1 = prototype.clone();
const square2 = prototype.clone();这样做的好处很明显:
修改模板就能影响所有复制的对象
避免重复的初始化代码
代码更易于维护
需要注意的是,JavaScript 语言本身就有原型机制,但这和我们讨论的设计模式不太一样。
JavaScript 的原型继承:
const squareTemplate = {
color: 'white',
row: 0,
file: 0,
piece: null,
occupySquare(piece) {
this.piece = piece;
}
};
// 创建基于原型的对象
const square1 = Object.create(squareTemplate);
const square2 = Object.create(squareTemplate);这里的区别很重要:
Object.create() 建立的是原型链关系,对象共享同一个原型
原型模式的 clone() 是创建完全独立的新对象
React 使用 Fiber 架构来管理组件更新。每次界面需要更新时,React 都会创建新的 Fiber 树。但如果每次都完全重新创建,性能会很差。
React 的解决方案就是使用原型模式的思想。我们来看看简化后的代码:
function createFiber(tag, pendingProps, key, mode) {
return {
tag,
key,
elementType: null,
type: null,
stateNode: null,
pendingProps,
memoizedProps: null,
// ... 很多其他属性
alternate: null, // 关键指针
};
}
function createWorkInProgressFiber(current) {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 首次创建,使用 Object.assign 快速复制
workInProgress = Object.assign(new FiberNode(), current);
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 复用现有对象,只更新必要属性
workInProgress.pendingProps = current.pendingProps;
workInProgress.type = current.type;
// ... 更新其他变化的属性
}
return workInProgress;
}这个设计的巧妙之处在于:
第一次更新时快速复制现有 Fiber
后续更新时复用对象,只修改变化的属性
通过 alternate 指针连接两棵树,实现双缓冲
Redux 的 immer 库也使用了类似的思想:
// 创建草案对象
const draft = Object.assign(Object.create(base), base);
// 修改草案
draft.user.name = '新名字';
// 最终生成新状态
const nextState = finalizeDraft(draft);相比深拷贝,这种方法大大减少了内存使用。
假设我们要管理应用配置:
传统方式(效率低):
const createConfig = () => ({
apiUrl: 'https://api.example.com',
timeout: 5000,
retry: 3,
headers: { 'Content-Type': 'application/json' }
});
// 每次都要重新初始化所有属性
const configs = Array(100).fill().map(() => createConfig());使用原型模式:
class ConfigFactory {
constructor(template) {
this.template = template;
}
clone(overrides = {}) {
return Object.assign({}, this.template, overrides);
}
}
const baseConfig = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
const configFactory = new ConfigFactory(baseConfig);
// 快速创建
const config1 = configFactory.clone();
const config2 = configFactory.clone();
// 创建变体
const devConfig = configFactory.clone({ apiUrl: 'http://localhost:3000' });性能对比:
传统方式:100 个配置 × 5 个属性 = 500 次赋值
原型模式:使用 Object.assign 优化,性能提升明显
使用 Object.assign 进行浅拷贝时要注意:
const template = {
user: { name: 'Alice' },
tags: ['js', 'react']
};
const copy1 = Object.assign({}, template);
const copy2 = Object.assign({}, template);
// 问题:嵌套对象是共享的
copy1.user.name = 'Bob';
console.log(template.user.name); // 也变成了 'Bob'解决方案:
// 需要完全独立时使用深拷贝
import _ from 'lodash';
class ConfigFactory {
constructor(template) {
this.template = template;
}
// 浅拷贝(性能好)
clone() {
return Object.assign({}, this.template);
}
// 深拷贝(完全独立)
deepClone() {
return _.cloneDeep(this.template);
}
}在实际项目中:
大多数情况使用浅拷贝就够了
只有在需要完全隔离时才使用深拷贝
考虑使用现成的工具库,如 lodash 的 cloneDeep
这里是一个可以在项目中直接使用的对象工厂:
class ObjectFactory {
constructor(template) {
this.template = template;
}
// 创建新对象
create(overrides = {}) {
return Object.assign({}, this.template, overrides);
}
// 批量创建
createBatch(count, overridesFn) {
return Array(count)
.fill()
.map((_, i) => this.create(overridesFn?.(i) || {}));
}
// 更新模板
updateTemplate(updates) {
this.template = Object.assign({}, this.template, updates);
}
}
// 使用示例
const userFactory = new ObjectFactory({
id: null,
name: '',
role: 'user',
permissions: []
});
// 批量创建用户
const users = userFactory.createBatch(1000, (i) => ({
id: i,
name: `用户${i}`
}));React Fiber 使用它来高效更新界面
状态管理库使用它来优化性能
它让代码更易于维护和扩展
下次当你看到 React 源码中的 Object.assign 或 alternate 时,就知道这是经过深思熟虑的设计选择。这种看似简单的模式,正是支撑现代前端框架高性能的关键技术之一。
理解这些底层原理,不仅能帮助你更好地使用框架,还能在需要自己实现复杂功能时,做出更明智的技术选择。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!
JS是一个非常有魅力的语言也是一个比较烦人的语言,主要就是因为他的特殊性灵活性。JS的原型链,需要深刻的研究才能搞懂。不要纠结细节吧。实在不行就按这个死背住,慢慢就理解了。总之吧就是一句话万物皆对象。
不学会怎么处理对象,你在 JavaScript 道路就就走不了多远。它们几乎是 JavaScript 编程语言每个方面的基础。事实上,学习如何创建对象可能是你刚开始学习的第一件事。
不学会怎么处理对象,你在 JavaScript 道路就就走不了多远。它们几乎是 JavaScript 编程语言每个方面的基础。事实上,学习如何创建对象可能是你刚开始学习的第一件事。
在工作中有时候会看到prototype和__proto__这两个属性,对这两个属性我一直比较蒙圈,但是我通过查阅相关资料,决定做一下总结加深自己的理解
在JS中,函数的本质就是对象,它与其他对象不同的是,创建它的构造函数与创建其他对象的构造函数不一样。那产生函数对象的构造函数是什么呢?是一个叫做Function的特殊函数,通过new Function 产生的对象就是一个函数。
JS中原型是为了实现代码重用的一种仿类机制,不过它跟类又完全不同。它通过给对象添加原型关系(即给某个对象添加__proto__属性)实现一个关联。把共有的方法和属性放到这个关联上即实现了JS的继承。简单来说就是一种委托机制
了解JavaScript中原型以及原型链只需要记住以下2点即可:对象都有__proto__属性,指向构造函数的prototype;构造函数\\函数都有prototype属性,指向构造函数的原型
对象:1,函数对象:有function创造出来的函数2,普通对象:除开函数对象之外的对象,都是普通对象**即普通对象obj是构造函数Object的一个实例,因此:
每个函数都有一个 prototype 属性,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 cat 和dog 的原型。
这里不介绍原型链。javascript 中有若干长得跟prototype,proto很想的属性/函数,这里简单总结一下他们都是啥,哪个是原型对象,哪个不是。[[Prototype]]这个对象的一个内置槽,对程序员是不可见
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!