<p><button onclick="testReactivity1()">Test</button> the basic mechanism of reactivity system in Vue 0.x - 2.x</p>
<p><button onclick="testProxy1()">Test</button> ES <code>Proxy</code></p>
<p><button onclick="testProxy2()">Test</button> ES <code>Proxy</code> with ES <code>Reflect</code></p>
<p><button onclick="testReactivity2()">Test</button> the basic mechanism of reactivity system in Vue 3.0 (without flag properties)</p>
<p>Please open the console panel to find out all the result.</p>
// basic mechanism of reactivity system in Vue 0.x - 2.x
const testReactivity1 = () => {
  // data
  const data = { x: 1, y: 2 }
  // real data and deps behind
  let realX = data.x
  let realY = data.y
  const realDepsX = []
  const realDepsY = []
  // make it reactive
  Object.defineProperty(data, 'x', {
    get() {
      trackX()
      return realX
    },
    set(v) {
      realX = v
      triggerX()
    }
  })
  Object.defineProperty(data, 'y', {
    get() {
      trackY()
      return realY
    },
    set(v) {
      realY = v
      triggerY()
    }
  })
  // track and trigger a property
  const trackX = () => {
    if (isDryRun && currentDep) {
      realDepsX.push(currentDep)
    }
  }
  const trackY = () => {
    if (isDryRun && currentDep) {
      realDepsY.push(currentDep)
    }
  }
  const triggerX = () => {
    realDepsX.forEach(dep => dep())
  }
  const triggerY = () => {
    realDepsY.forEach(dep => dep())
  }
  // observe a function
  let isDryRun = false
  let currentDep = null
  const observe = fn => {
    isDryRun = true
    currentDep = fn
    fn()
    currentDep = null
    isDryRun = false
  }
  // define 3 functions
  const depA = () => console.log(`x = ${data.x}`)
  const depB = () => console.log(`y = ${data.y}`)
  const depC = () => console.log(`x + y = ${data.x + data.y}`)
  // dry-run all dependents
  observe(depA)
  observe(depB)
  observe(depC)
  // output: x = 1, y = 2, x + y = 3
  // mutate data
  data.x = 3
  // output: x = 3, x + y = 5
  data.y = 4
  // output: y = 4, x + y = 7
}

// demo of ES Proxy
const testProxy1 = () => {
  const data = { x: 1, y: 2 }
  // all behaviors of a proxy by operation types
  const handlers = {
    get(data, propName, proxy) {
      console.log(`Get ${propName}: ${data[propName]}!`)
      return data[propName]
    },
    // has(data, propName) { ... },
    // set(data, propName, value, proxy) { ... },
    // deleteProperty(data, propName) { ... },
    // ...
  }
  // create a proxy object for the data
  const proxy = new Proxy(data, handlers)
  // print: 'Get x: 1' and return `1`
  console.log(proxy.x)
}

// demo of ES Proxy + ES Reflect
const testProxy2 = () => {
  const data = { x: 1, y: 2 }
  // all behaviors of a proxy by operation types
  const handlers = {
    get(data, propName, proxy) {
      console.log(`Get ${propName}: ${data[propName]}!`)
      // same behavior as before
      return Reflect.get(data, propName, proxy)
    },
    has(...arguments) { return Reflect.set(...arguments) },
    set(...arguments) { return Reflect.set(...arguments) },
    deleteProperty(...arguments) { return Reflect.set(...arguments) },
    // ...
  }
  // create a proxy object for the data
  const proxy = new Proxy(data, handlers)
  // print: 'Get x: 1' and return `1`
  console.log(proxy.x)
}

// basic mechanism of reactivity system in Vue 3.0 (without flag properties)
const testReactivity2 = () => {
  // a WeakMap to record dependets
  const dependentMap = new WeakMap()
  
  // track and trigger a property
  const track = (type, data, propName) => {
    if (isDryRun && currentFn) {
      if (!dependentMap.has(data)) {
        dependentMap.set(data, new Map())
      }
      if (!dependentMap.get(data).has(propName)) {
        dependentMap.get(data).set(propName, new Set())
      }
      dependentMap.get(data).get(propName).add(currentFn)
    }
  }
  const trigger = (type, data, propName) => {
    dependentMap.get(data).get(propName).forEach(fn => fn())
  }
  
  // observe
  let isDryRun = false
  let currentFn = null
  const observe = fn => {
    isDryRun = true
    currentFn = fn
    fn()
    currentFn = null
    isDryRun = false
  }

  // all behaviors of a proxy by operation types
  const handlers = {
    get(...arguments) { track('get', ...arguments); return Reflect.get(...arguments) },
    has(...arguments) { track('has', ...arguments); return Reflect.set(...arguments) },
    set(...arguments) { Reflect.set(...arguments); trigger('set', ...arguments) },
    deleteProperty(...arguments) {
      Reflect.set(...arguments);
      trigger('delete', ...arguments)
    },
    // ...
  }

  // make data and arr reactive
  const data = { x: 1, y: 2 }
  const proxy = new Proxy(data, handlers)
  const arr = [1, 2, 3]
  const arrProxy = new Proxy(arr, handlers)
  
  // observe functions
  const depA = () => console.log(`x = ${proxy.x}`)
  const depB = () => console.log(`y = ${proxy.y}`)
  const depC = () => console.log(`x + y = ${proxy.x + proxy.y}`)
  const depD = () => {
   let sum = 0
   for (let i = 0; i < arrProxy.length; i++) {
    sum += arrProxy[i]
   }
   console.log(`sum = ${sum}`)
  }
  
  // dry-run all dependents
  observe(depA)
  observe(depB)
  observe(depC)
  observe(depD)
  // output: x = 1, y = 2, x + y = 3, sum = 6
  
  // mutate data
  proxy.x = 3
  // output: x = 3, x + y = 5
  
  arrProxy[1] = 4
  // output: sum = 8
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.