推荐答案
Proxy 对象可以通过拦截对象的操作,并在这些操作发生时执行自定义的逻辑,从而实现数据绑定。核心思路是利用 get
和 set
拦截器,在读取属性时收集依赖,在设置属性时触发更新。以下是实现数据绑定的基本步骤:
- 创建一个
reactive
函数,接收一个普通对象作为参数,返回一个响应式对象(Proxy 对象)。 - 在
reactive
函数内部,使用Proxy
创建一个代理对象。 - 在
Proxy
的get
拦截器中,收集当前正在执行的副作用函数(如渲染函数)到依赖集合中。 - 在
Proxy
的set
拦截器中,当属性值发生改变时,遍历依赖集合,执行所有收集到的副作用函数,从而触发更新。
-- -------------------- ---- ------- -------- ---------------- - ----- ------- - --- ------ -- ---------- --- -------- ------------- ---- - --- ---- - ----------------- -- ------- - ---- - --- ------ ---------------- ------ - -- -------------- - -- ------------ ---------- ----------------------- - - -------- --------------- ---- - ----- ---- - ----------------- -- ------ - ------------------- -- ---------- - - ----- ----- - --- ------------- - ----------- ---- --------- - ------------- ----- ------ ------------------- ---- ---------- -- ----------- ---- ------ --------- - -------------- --- ------ ------ ----- -- ----------- ----- ------ - ------------------- ---- ------ ---------- --------------- ----- ------ ------- - --- ------ ------ - --- ------------ - ----- -------- ---------- - ------------ - --- ----- -- ----------- ------------ - ----- - -- -- ----- ---- - - ------ - -- ----- ------------ - --------------- --- ----------- - -- --------- -- - -------------------- -------- -------------------- -------------- --- --------------------- --------------------- --------------------------- ------------
本题详细解读
什么是 Proxy
Proxy
是 ES6 中新增的一个对象,可以用来创建一个对象的代理,从而拦截并自定义对象的基本操作(如属性读取、赋值、删除等)。 Proxy
的基本语法如下:
const proxy = new Proxy(target, handler);
target
: 需要代理的目标对象。handler
: 一个对象,包含用于拦截操作的函数(称为陷阱)。
get
和 set
陷阱
get
和 set
是 handler
对象中常用的陷阱:
get(target, key, receiver)
: 当访问代理对象的属性时会被调用。target
: 目标对象。key
: 被访问的属性名。receiver
: 代理对象本身(或继承自代理对象的子对象)。
set(target, key, value, receiver)
: 当设置代理对象的属性时会被调用。target
: 目标对象。key
: 被设置的属性名。value
: 要设置的新值。receiver
: 代理对象本身。
数据绑定原理
- 响应式对象:
reactive
函数返回的 Proxy 对象。 - 依赖收集:
- 在
get
陷阱中,当访问一个响应式对象的属性时,会执行track
函数。 track
函数会检查当前是否有正在执行的副作用函数(通过activeEffect
判断)。- 如果存在,则将该副作用函数添加到该属性的依赖集合中(
depsMap
)。
- 在
- 触发更新:
- 在
set
陷阱中,当修改响应式对象的属性时,会执行trigger
函数。 trigger
函数会遍历该属性的依赖集合,并执行其中所有的副作用函数,从而触发视图的更新(重新渲染)。
- 在
- 副作用函数:
effect
函数用于定义副作用函数,例如render
函数。- 在
effect
函数执行期间,会将当前正在执行的副作用函数赋值给activeEffect
。
代码解释
depsMap
: 一个Map
对象,用于存储属性和依赖集合之间的关系。Key 是属性名,Value 是一个Set
,存储依赖该属性的所有副作用函数。track
: 用于收集依赖的函数。它会查找属性的依赖集合,如果不存在则创建新的,并将当前正在执行的副作用函数添加到集合中。trigger
: 用于触发更新的函数。它会查找属性的依赖集合,并执行集合中的所有副作用函数。reactive
: 创建响应式对象的函数,返回一个Proxy
对象,其中get
和set
拦截器分别调用track
和trigger
。effect
: 用于创建副作用函数的函数,会设置activeEffect
指向当前函数,执行函数,并将activeEffect
设置为null
。
代码示例运行流程
- 创建
data
对象 和reactiveData
响应式对象。 - 执行
effect
函数
- 设置
activeEffect
为当前的渲染函数 - 执行渲染函数(console.log输出,记录第一次渲染)
- 设置
activeEffect
为null
- 修改
reactiveData.count
两次
- 每次修改都会执行
set
陷阱 set
陷阱执行trigger
函数,通知count
属性对应的所有副作用函数执行(也就是渲染函数)。- 每次渲染函数都会被执行,并输出当前 count 的值
- 最后,输出渲染次数
renderCount
,值为 3,因为首次 effect 执行渲染了一次,两次 set 触发了渲染。
优点和缺点
优点:
- 简单易懂: 使用
Proxy
实现数据绑定代码相对简洁,更容易理解。 - 拦截能力强:
Proxy
可以拦截对象的所有基本操作,功能强大。 - 高性能: 相对于
Object.defineProperty
,Proxy
在某些情况下性能更高。
缺点:
- 兼容性问题:
Proxy
在 IE 浏览器中不兼容,需要使用 polyfill。 - 深层响应式: 如果需要实现对嵌套对象的深层响应式,需要递归地使用
reactive
函数,稍显复杂。