推荐答案
// www.javascriptcn.com code example
function reactive(obj) {
const proxy = new Proxy(obj, {
get(target, key) {
track(target, key); // 依赖收集
return Reflect.get(target, key);
},
set(target, key, value) {
const oldValue = Reflect.get(target, key);
if(oldValue !== value) {
Reflect.set(target,key,value);
trigger(target, key); // 触发更新
}
return true; // 表示设置成功
}
});
return proxy;
}
let activeEffect = null;
const targetMap = new WeakMap();
function track(target, key) {
if(!activeEffect) return;
let depsMap = targetMap.get(target);
if(!depsMap){
depsMap = new Map();
targetMap.set(target, depsMap);
}
let deps = depsMap.get(key);
if(!deps){
deps = new Set();
depsMap.set(key, deps);
}
deps.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if(!depsMap) return;
const deps = depsMap.get(key);
if(!deps) return;
deps.forEach(effect => effect());
}
function effect(fn) {
activeEffect = fn;
fn(); // 首次执行,触发get进行依赖收集
activeEffect = null; // 清除 activeEffect
}
// 示例
const data = reactive({ count: 0, text: 'hello' });
effect(() => {
console.log(`count is ${data.count}, text is ${data.text}`);
});
data.count++; // 控制台输出:count is 1, text is hello
data.text = 'world' // 控制台输出:count is 1, text is world本题详细解读
核心概念
响应式系统的核心思想是:当数据发生变化时,自动更新视图或其他依赖于这些数据的部分。利用 Proxy 对象,我们可以拦截对对象属性的读取 (get) 和设置 (set) 操作,从而实现依赖收集和触发更新。
实现步骤
reactive(obj)函数:- 使用
Proxy包装传入的对象obj。 get(target, key)拦截器:- 调用
track(target, key)函数,用于收集依赖。 - 返回属性值:使用
Reflect.get(target, key)获取。
- 调用
set(target, key, value)拦截器:- 获取旧值,判断新旧值是否发生改变,没有改变则不更新。
- 设置属性值:使用
Reflect.set(target, key, value)设置。 - 调用
trigger(target, key)函数,触发更新。 - 返回
true,表示设置成功。
- 使用
track(target, key)函数:- 用于依赖收集。
- 使用
WeakMap(targetMap) 来存储每个对象及其属性的依赖关系。 - 如果当前没有激活的
effect函数(activeEffect),则直接返回,不进行依赖收集。 targetMap中存储的key为目标对象,value为Map类型数据,该Map的键为属性名,值为该属性的依赖集合(Set)。- 如果
targetMap中不存在当前target的记录,则创建新的Map,并将target和Map存储到targetMap中。 - 如果
Map中不存在当前key的记录,则创建新的Set,并将key和Set存储到该Map中。 - 将当前激活的
effect函数(activeEffect) 添加到该属性的依赖集合Set中。
trigger(target, key)函数:- 用于触发更新。
- 从
targetMap中获取目标对象的所有依赖。 - 如果不存在目标对象的依赖,则直接返回。
- 获取目标属性的依赖集合。
- 如果目标属性不存在依赖,则直接返回。
- 遍历该属性的依赖集合,并执行每个
effect函数。
effect(fn)函数:- 接收一个函数
fn作为参数,该函数表示需要响应式更新的逻辑。 - 将
fn赋值给activeEffect,表示当前正在执行的effect函数。 - 执行
fn(),触发Proxy的get拦截器,从而进行依赖收集。 - 执行完成后,清除
activeEffect,防止错误的依赖收集。
- 接收一个函数
示例解析
- 创建响应式对象
data:const data = reactive({ count: 0, text: 'hello' }); - 定义一个
effect函数,输出data的count和text属性:effect(() => { console.log(`count is ${data.count}, text is ${data.text}`); }); - 修改
data.count:data.count++;,触发Proxy的set拦截器,然后调用trigger函数,执行依赖effect函数,控制台输出count is 1, text is hello。 - 修改
data.text:data.text = 'world',触发Proxy的set拦截器,然后调用trigger函数,执行依赖effect函数,控制台输出count is 1, text is world。
优势
- 简洁性: 使用
Proxy可以简洁地实现响应式系统,避免了传统的Object.defineProperty的繁琐。 - 拦截所有操作:
Proxy可以拦截in,delete等操作,比Object.defineProperty更强大。 - 更好的性能:
Proxy可以拦截数组的操作,从而实现响应式数组,而Object.defineProperty需要额外处理数组的变更。