教你手写实现数据绑定的响应式原理
数据绑定响应式原理
前言
- 请带着以下 2 个问题进行思考 ~
- 为什么要探究响应式绑定的实现原理呢?
- 理解响应式绑定的底层运行机制能带给我们什么?
那么为什么要探究响应式绑定的实现原理呢?
- 帮助理解 MVVM 模式:响应式绑定是 MVVM 架构的基础,掌握其原理有助于更深入地理解 MVVM 的工作方式
- 实现自定义数据绑定:了解原理后,我们可以自己实现一个简单的响应式数据绑定机制
- 分析性能优化方案:掌握原理后,可以对性能瓶颈进行分析,选择更优的实现方案
- 掌握核心技术要点:Object.defineProperty、代理PROXY、观察者模式等是响应式关键技术
- 提升框架使用能力:掌握原理后可以更灵活地使用 Vue、React 等框架的响应式编程
理解响应式绑定的底层运行机制能带给我们什么?
- 阅读结束你可以回过头想想带给你了什么 接下来让我们进入正题,,开始分析响应式绑定原理 ~
实现一个简单的响应式编程机制
- 响应式编程是当数据模型发生变化时,自动更新视图的一种编程范式。
- 它可以极大地简化状态管理。
- 今天我们来手动实现一个简单的响应式编程机制,加深对其工作原理的理解。
- 核心代码实现上,我们创建了一个 Depend 类,它用来收集依赖于某个数据的观察者函数。
- 我们还创建了一个 Reactive 函数,通过 Proxy 或 Object.defineProperty 的方式把对象属性转换成 getter 和 setter。
小二 ~ 上代码
let activeReactiveFn = null; // 响应式函数
// 定义一个数据绑定收集类
class Depend {
constructor() {
this.reactiveFns = new Set(); // 使用 Set 主要解决保证收集的响应式函数不重复
}
// 收集响应式函数
depend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn);
}
}
// 通知更新
notify() {
this.reactiveFns.forEach((fn) => fn());
}
}
// 响应式函数
function watchFn(fn) {
activeReactiveFn = fn;
fn();
activeReactiveFn = null;
}
// 收集数据更新方法(重点~)
let targetMap = new WeakMap();
function getDepend(target, key) {
let map = targetMap.get(target);
if (!map) {
map = new Map();
targetMap.set(target, map);
}
let depend = map.get(key);
if (!depend) {
depend = new Depend();
map.set(key, depend);
}
return depend;
}
// 模拟 Vue3 实现 reactive()
function reactive(obj) {
Object.keys(obj).forEach((key) => {
let value = obj[key];
Object.defineProperty(obj, key, {
get: function () {
const depend = getDepend(obj, key);
depend.depend();
return value;
},
set: function (newValue) {
value = newValue;
const depend = getDepend(obj, key);
depend.notify();
}
});
});
return obj;
}
const info = reactive({
name: 'codewhite',
profession: 'programmer'
});
watchFn(function () {
console.log('-------------------第一个 name 函数开始--------------------');
console.log('Hello World');
console.log('info', info.profession);
console.log('info', info.name);
console.log('-------------------第一个 name 函数结束--------------------');
});
info.name = 'gucy';
那么上面的代码的工作流程是怎样的呢?
- 解释代码工作流程
- 在调用 watchFn 注册观察者函数时,通过 activeReactiveFn 标记当前观察者函数。
- 在 getter 中收集 Depend 依赖,即加到依赖项中。
- 当 setter 触发时,调 notify 方法,遍历执行所有依赖函数。
- 这样就实现了数据变化驱动视图更新的响应式效果。
- 我们也可以注意到,这里我使用了 WeakMap 来存储依赖关系。因为 WeakMap 的键是弱引用,不会影响垃圾回收,可以防止内存泄漏。
- Proxy 和 Object.defineProperty 都可以用来实现响应式转换,前者兼容性稍差但更简洁。我们也可以扩展功能,实现深层嵌套对象的响应式转换。
- 通过自己实现一个简单的响应式绑定模型,可以加深对响应式编程的理解,理解依赖收集和触发更新的内部原理。这种理解可以帮助我们更好地使用 Vue、React 等框架的响应式系统。
读到这里,是不是看见 reactive() 很熟悉呢?
- 是的,在我们 Vue3 开发中复杂的响应式数据绑定就是通过 reactive 来实现的
- 那么以上的代码是基于 Vue2 使用了 Object.defineProperty 来实现的,我们能不能写一个 基于 proxy 来实现呢?当然可以,接下来我们继续!
Proxy 运行的机制分析
我们先来学习了解一下 Proxy
- Proxy 的作用
- 可以监听对象身上发生的所有操作,如属性读取、函数调用等。
- 可以实现基于对象操作的拦截和自定义操作,实现数据绑定、严格控制属性访问等高级功能。
- 可以作为对象的访问管道,对外部的访问进行过滤和改写。
- 可以扩展和改写原有对象的默认行为。
- 可以实现完善的对象访问日志和错误处理机制。
- Proxy 的使用
const p = new Proxy(target, handler);
- 参数解释
- target:要监控的目标对象
- handler:一个对象,定义了代理的行为 handler(捕获器)
- 常用捕获器(trap)有:
- get - 监听对象属性访问
- set - 监听对象属性设置
- apply - 监听函数调用
- construct - 监听 new 操作符
- deleteProperty - 监听 delete 操作
- getPrototypeOf - 监听 Object.getPrototypeOf 操作
学会了 Proxy 请看下面的实现代码吧!
function reactive(obj) {
return new Proxy(obj, {
get: function (target, key, receiver) {
// 根据 target, key 获取对应的 depend
const depend = getDepend(target, key);
depend.depend();
return Reflect.get(target, key, receiver);
},
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver);
const depend = getDepend(target, key);
depend.notify();
}
});
}
结束语
学会了吧,带着上述两个问题阅读完后,可自行思考,也去写一个数据响应式玩玩吧,再会 ~
转载自:https://juejin.cn/post/7256610327131160636