The creation process of responsive proxies
Core Concepts of Reactive Proxies
Vue 3's reactivity system is based on Proxy, which offers significant improvements over Vue 2's Object.defineProperty
. Proxy can intercept various operations on objects, including property access, assignment, and deletion, enabling Vue to track dependencies and trigger updates more precisely. The creation of reactive proxies primarily occurs during the invocation of the reactive()
function.
const obj = reactive({ count: 0 })
Execution Flow of the reactive()
Function
When reactive()
is called, Vue performs the following steps:
- First checks if the passed value is already a reactive proxy; if so, returns it directly.
- Checks the value type—only object types can be proxied.
- Creates a proxy object using the
Proxy
constructor. - Sets the proxy object's identifier property
__v_isReactive
.
function reactive(target) {
// If already a reactive proxy, return directly
if (target && target.__v_isReactive) {
return target
}
// Only objects can be proxied
if (!isObject(target)) {
return target
}
// Create the proxy
const proxy = new Proxy(
target,
baseHandlers
)
// Set the reactive identifier
def(proxy, '__v_isReactive', true)
return proxy
}
Base Proxy Handlers (baseHandlers
)
baseHandlers
is the handler object for Proxy, defining various interception behaviors. Vue 3 primarily uses the following interceptors:
const baseHandlers = {
get: createGetter(),
set: createSetter(),
has,
deleteProperty,
ownKeys
}
Implementation of the get
Interceptor
The get
interceptor is responsible for dependency tracking and return value handling. When accessing a property of a reactive object:
- Tracks the currently executing effect.
- If the property value is an object, recursively converts it to reactive.
- Returns the property value.
function createGetter() {
return function get(target, key, receiver) {
// Dependency tracking
track(target, key)
const res = Reflect.get(target, key, receiver)
// If the value is an object, recursively proxy it
if (isObject(res)) {
return reactive(res)
}
return res
}
}
Implementation of the set
Interceptor
The set
interceptor is responsible for triggering updates:
- Checks if the value has changed.
- Sets the new value.
- Triggers dependency updates.
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key]
// Set the value
const result = Reflect.set(target, key, value, receiver)
// Only trigger updates if the value has changed
if (hasChanged(value, oldValue)) {
trigger(target, key)
}
return result
}
}
Dependency Tracking and Trigger Mechanism
Implementation of the track
Function
The track
function associates the currently running effect with the accessed property:
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)
}
Implementation of the trigger
Function
The trigger
function finds all effects dependent on the property and executes them:
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach(effect => effect.run())
}
Handling Special Cases
Proxy Handling for Arrays
Arrays require special handling for the following methods:
- Methods that change the array length, such as
push
,pop
,shift
,unshift
, andsplice
. - Search methods like
includes
,indexOf
, andlastIndexOf
.
const arrayInstrumentations = {}
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
arrayInstrumentations[method] = function(...args) {
// Pause dependency tracking
pauseTracking()
const res = Array.prototype[method].apply(this, args)
// Resume dependency tracking
resetTracking()
return res
}
})
Wrapping Primitive Values
For primitive values like numbers and strings, Vue provides ref()
to create reactive references:
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')
}
}
}
Performance Optimization Strategies
Proxy Caching
Vue caches created proxies to avoid repeatedly proxying the same object:
const reactiveMap = new WeakMap()
function reactive(target) {
// Check the cache
const existingProxy = reactiveMap.get(target)
if (existingProxy) {
return existingProxy
}
// Create a new proxy
const proxy = createReactiveObject(target)
reactiveMap.set(target, proxy)
return proxy
}
Shallow Reactivity
For scenarios where deep reactivity is unnecessary, Vue provides shallowReactive
:
function shallowReactive(target) {
return createReactiveObject(
target,
shallowReactiveHandlers
)
}
const shallowReactiveHandlers = {
get: function(target, key, receiver) {
// Skip deep conversion
const res = Reflect.get(target, key, receiver)
track(target, key)
return res
},
// ...other interceptors
}
Edge Cases in the Reactivity System
Handling Non-Proxiable Objects
Certain special objects like Date
and RegExp
cannot be proxied; Vue returns their original values:
function canProxy(target) {
return (
target instanceof Object &&
!(target instanceof Date) &&
!(target instanceof RegExp)
)
}
Handling Circular References
Vue automatically handles circular references through the proxy caching mechanism:
const obj = { self: null }
obj.self = obj
const proxy = reactive(obj)
console.log(proxy.self === proxy) // true
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:effect函数的内部工作原理
下一篇:嵌套响应对象的处理方式