推荐答案
-- -------------------- ---- ------- -------- ------------- - ----- ----- - --- ---------- - ----------- ---- - ------------- ----- -- ---- ------ ------------------- ----- -- ----------- ---- ------ - ----- -------- - ------------------- ----- ----------- --- ------ - ------------------------------ --------------- ----- -- ---- - ------ ----- -- ------ - --- ------ ------ - --- ------------ - ----- ----- --------- - --- ---------- -------- ------------- ---- - ----------------- ------- --- ------- - ---------------------- ------------- ------- - --- ------ --------------------- --------- - --- ---- - ----------------- ---------- ---- - --- ------ ---------------- ------ - ----------------------- - -------- --------------- ---- - ----- ------- - ---------------------- ------------ ------- ----- ---- - ----------------- --------- ------- ------------------- -- ---------- - -------- ---------- - ------------ - --- ----- -- ---------------- ------------ - ----- -- -- ------------ - -- -- ----- ---- - ---------- ------ -- ----- ------- --- --------- -- - ------------------ -- -------------- ---- -- --------------- --- ------------- -- ----------- -- -- ---- -- ----- --------- - ------- -- ----------- -- -- ---- -- -----
本题详细解读
核心概念
响应式系统的核心思想是:当数据发生变化时,自动更新视图或其他依赖于这些数据的部分。利用 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
需要额外处理数组的变更。