响应式代理的创建过程
响应式代理的核心概念
Vue3的响应式系统基于Proxy实现,与Vue2的Object.defineProperty相比有显著改进。Proxy可以拦截对象的各种操作,包括属性访问、赋值、删除等,这使得Vue能够更精确地追踪依赖和触发更新。响应式代理的创建过程主要发生在reactive()函数调用时。
const obj = reactive({ count: 0 })
reactive()函数的执行流程
当调用reactive()时,Vue会执行以下步骤:
- 首先检查传入的值是否已经是响应式代理,如果是则直接返回
- 检查值类型,只有对象类型才能被代理
- 通过Proxy构造函数创建代理对象
- 设置代理对象的标识属性__v_isReactive
function reactive(target) {
// 如果已经是响应式代理,直接返回
if (target && target.__v_isReactive) {
return target
}
// 只能代理对象
if (!isObject(target)) {
return target
}
// 创建代理
const proxy = new Proxy(
target,
baseHandlers
)
// 设置响应式标识
def(proxy, '__v_isReactive', true)
return proxy
}
基础代理处理器(baseHandlers)
baseHandlers是Proxy的处理器对象,定义了各种拦截行为。Vue3主要使用以下拦截器:
const baseHandlers = {
get: createGetter(),
set: createSetter(),
has,
deleteProperty,
ownKeys
}
get拦截器的实现
get拦截器负责依赖收集和返回值处理。当访问响应式对象的属性时:
- 追踪当前正在执行的effect
- 如果属性值是对象,递归转换为响应式
- 返回属性值
function createGetter() {
return function get(target, key, receiver) {
// 依赖收集
track(target, key)
const res = Reflect.get(target, key, receiver)
// 如果值是对象,递归代理
if (isObject(res)) {
return reactive(res)
}
return res
}
}
set拦截器的实现
set拦截器负责触发更新:
- 检查值是否变化
- 设置新值
- 触发依赖更新
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key]
// 设置值
const result = Reflect.set(target, key, value, receiver)
// 只有值变化时才触发更新
if (hasChanged(value, oldValue)) {
trigger(target, key)
}
return result
}
}
依赖收集与触发机制
track函数实现
track函数将当前正在运行的effect与访问的属性建立关联:
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
trigger函数实现
trigger函数查找所有依赖该属性的effect并执行:
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach(effect => effect.run())
}
特殊情况的处理
数组的代理处理
数组需要特殊处理以下方法:
- push/pop/shift/unshift/splice等会改变数组长度的方法
- includes/indexOf/lastIndexOf等查找方法
const arrayInstrumentations = {}
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
arrayInstrumentations[method] = function(...args) {
// 暂停依赖收集
pauseTracking()
const res = Array.prototype[method].apply(this, args)
// 恢复依赖收集
resetTracking()
return res
}
})
原始值包装
对于原始值如number/string,Vue提供了ref()来创建响应式引用:
function ref(value) {
return createRef(value)
}
function createRef(rawValue) {
return new RefImpl(rawValue)
}
class RefImpl {
constructor(value) {
this._value = convert(value)
this.__v_isRef = true
}
get value() {
track(this, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(newVal, this._value)) {
this._value = convert(newVal)
trigger(this, 'value')
}
}
}
性能优化策略
代理缓存
Vue会缓存已创建的代理,避免重复代理同一对象:
const reactiveMap = new WeakMap()
function reactive(target) {
// 检查缓存
const existingProxy = reactiveMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建新代理
const proxy = createReactiveObject(target)
reactiveMap.set(target, proxy)
return proxy
}
浅响应式
对于不需要深度响应的场景,Vue提供了shallowReactive:
function shallowReactive(target) {
return createReactiveObject(
target,
shallowReactiveHandlers
)
}
const shallowReactiveHandlers = {
get: function(target, key, receiver) {
// 不进行深度转换
const res = Reflect.get(target, key, receiver)
track(target, key)
return res
},
// ...其他拦截器
}
响应式系统的边界情况
不可代理对象的处理
某些特殊对象如Date、RegExp等不能被Proxy代理,Vue会返回原始值:
function canProxy(target) {
return (
target instanceof Object &&
!(target instanceof Date) &&
!(target instanceof RegExp)
)
}
循环引用处理
Vue通过代理缓存机制自动处理循环引用:
const obj = { self: null }
obj.self = obj
const proxy = reactive(obj)
console.log(proxy.self === proxy) // true
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:effect函数的内部工作原理
下一篇:嵌套响应对象的处理方式