阿里云主机折上折
  • 微信号
Current Site:Index > The creation process of responsive proxies

The creation process of responsive proxies

Author:Chuan Chen 阅读数:36426人阅读 分类: Vue.js

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:

  1. First checks if the passed value is already a reactive proxy; if so, returns it directly.
  2. Checks the value type—only object types can be proxied.
  3. Creates a proxy object using the Proxy constructor.
  4. 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:

  1. Tracks the currently executing effect.
  2. If the property value is an object, recursively converts it to reactive.
  3. 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:

  1. Checks if the value has changed.
  2. Sets the new value.
  3. 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, and splice.
  • Search methods like includes, indexOf, and lastIndexOf.
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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.