Learned notes from coderwhy’s JavaScript Advanced Syntax video lesson

If there is a mistake or inappropriate place, please forgive me, welcome to point out and expand, thank you

First, the realization of Vue3 response principle

  • The following steps are a step-by-step process of derivation and realization.

Step 1: Encapsulation of reactive functions

// Encapsulate reactive functions
const reactiveFns = []
function watchFn(fn) {
  reactiveFns.push(fn)
}

const obj = {
  name: 'xwl'.age: 18
}

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  console.log('obj. Name value: ' + obj.name)
})
watchFn(function () {
  console.log('obj.name2: ' + obj.name)
})

obj.name = 'xxx'
reactiveFns.forEach(item= > {
  item()
})
Copy the code

Step 2: Rely on the encapsulation of the collection class

The first step is not easy to manage with arrays, so use a class to manage.

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item= > {
      item()
    })
  }
}

// Encapsulate reactive functions
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

const obj = {
  name: 'xwl'.age: 18
}

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  console.log('obj. Name value: ' + obj.name)
})
watchFn(function () {
  console.log('obj.name2: ' + obj.name)
})

obj.name = 'xxx'
depend.notify()
Copy the code

Step 3: Automatically listen for object changes

Use Proxy and Reflect to listen for changes in the object.

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item= > {
      item()
    })
  }
}

// Encapsulate reactive functions
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

const obj = {
  name: 'xwl'.age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    depend.notify()
  }
})

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  console.log('obj. Name value: ' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2: ' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age value: ' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
Copy the code

Step 4: Rely on collection management

This requires the use of Map and WeakMap. Objproxy. age = 20, all reactive functions are executed once, without differentiating between name and age.

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item= > {
      item()
    })
  }
}

// Encapsulate reactive functions
const depend = new Depend()
function watchFn(fn) {
  depend.addDepend(fn)
}

// Get the wrapper of depend
const targetMap = new WeakMap(a)function getDepend(target, key) {
  let map = targetMap.get(target)
  if(! map) { map =new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if(! depend) { depend =new Depend()
    map.set(key, depend)
  }

  return depend
}

const obj = {
  name: 'xwl'.age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  console.log('obj. Name value: ' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2: ' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age value: ' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
Copy the code

Step 5: Collect dependencies correctly

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(fn) {
    this.reactiveFns.push(fn)
  }

  notify() {
    this.reactiveFns.forEach(item= > {
      item()
    })
  }
}

// Encapsulate reactive functions
let activeReactiveFn = null AddDepend (activeReactiveFn) is used to pass parameters to depend. AddDepend (activeReactiveFn)
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// Get the wrapper of depend
const targetMap = new WeakMap(a)function getDepend(target, key) {
  let map = targetMap.get(target)
  if(! map) { map =new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if(! depend) { depend =new Depend()
    map.set(key, depend)
  }

  return depend
}

const obj = {
  name: 'xwl'.age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    const depend = getDepend(target, key)
    depend.addDepend(activeReactiveFn)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  console.log('obj. Name value: ' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2: ' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age value: ' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
Copy the code

Step 6: Refactor the Depend class

There are two modifications to this step:

  • Change todepend. AddDepend (activeReactiveFn) to todepend. Depend() and define a global variable activeReactiveFn to solve the transfer function problem;
  • Change the normal array [] to new Set() to remove duplicates.
let activeReactiveFn = null

class Depend {
  constructor() {
    //
    this.reactiveFns = new Set()}// addDepend(fn) {
  // this.reactiveFns.push(fn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(item= > {
      item()
    })
  }
}

// Encapsulate reactive functions
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// Get the wrapper of depend
const targetMap = new WeakMap(a)function getDepend(target, key) {
  let map = targetMap.get(target)
  if(! map) { map =new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if(! depend) { depend =new Depend()
    map.set(key, depend)
  }

  return depend
}

const obj = {
  name: 'xwl'.age: 18
}

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    const depend = getDepend(target, key)
    // depend.addDepend(activeReactiveFn)
    depend.depend()
    return Reflect.get(target, key, receiver)
  },
  set(target, key, newValue, receiver) {
    Reflect.set(target, key, newValue, receiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  // When objproxy.name is used twice, duplicate reactive functions are added to the array, so new Set() is used.
  console.log('obj. Name value: ' + objProxy.name)
  console.log('obj. Name value 111: ' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2: ' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age value: ' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20
Copy the code

Step 7 (final step) : the object’s reactive operation vue3

let activeReactiveFn = null

class Depend {
  constructor() {
    //
    this.reactiveFns = new Set()}// addDepend(fn) {
  // this.reactiveFns.push(fn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(item= > {
      item()
    })
  }
}

// Encapsulate reactive functions
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// Get the wrapper of depend
const targetMap = new WeakMap(a)function getDepend(target, key) {
  let map = targetMap.get(target)
  if(! map) { map =new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if(! depend) { depend =new Depend()
    map.set(key, depend)
  }

  return depend
}

// The first way
// const obj = {
// name: 'xwl',
// age: 18
// }
// const objProxy = reactive(obj)

// Second way: shorthand
const objProxy = reactive({
  name: 'xwl'.age: 18
})

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      const depend = getDepend(target, key)
      // depend.addDepend(activeReactiveFn)
      depend.depend()
      return Reflect.get(target, key, receiver)
    },
    set(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver)
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  // When objproxy.name is used twice, duplicate reactive functions are added to the array, so new Set() is used.
  console.log('obj. Name value: ' + objProxy.name)
  console.log('obj. Name value 111: ' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2: ' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age value: ' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20

// ---------------------------------------------------
const infoProxy = reactive({
  address: 'gd'
})

watchFn(function () {
  console.log(The value of '-----info.address: ' + infoProxy.address)
})

infoProxy.address = 'bj'
Copy the code

Realization of Vue2 response formula principle

Vue2 and vue3 implement similar logic. Vue2 uses Object.defineProperty() and vue3 uses Proxy and Reflect.

let activeReactiveFn = null

class Depend {
  constructor() {
    //
    this.reactiveFns = new Set()}// addDepend(fn) {
  // this.reactiveFns.push(fn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(item= > {
      item()
    })
  }
}

// Encapsulate reactive functions
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// Get the wrapper of depend
const targetMap = new WeakMap(a)function getDepend(target, key) {
  let map = targetMap.get(target)
  if(! map) { map =new Map()
    targetMap.set(target, map)
  }

  let depend = map.get(key)
  if(! depend) { depend =new Depend()
    map.set(key, depend)
  }

  return depend
}

// The first way
// const obj = {
// name: 'xwl',
// age: 18
// }
// const objProxy = reactive(obj)

// Second way: shorthand
const objProxy = reactive({
  name: 'xwl'.age: 18
})

function reactive(obj) {
  Object.keys(obj).forEach(key= > {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function () {
        const depend = getDepend(obj, key)
        depend.depend()
        return value
      },
      set: function (newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend.notify()
      }
    })
  })
  return obj
}

// watchFn() encapsulates the function to distinguish it from normal functions
watchFn(function () {
  // When objproxy.name is used twice, duplicate reactive functions are added to the array, so new Set() is used.
  console.log('obj. Name value: ' + objProxy.name)
  console.log('obj. Name value 111: ' + objProxy.name)
})
watchFn(function () {
  console.log('obj.name2: ' + objProxy.name)
})

watchFn(function () {
  console.log('obj.age value: ' + objProxy.age)
})

objProxy.name = 'xxx'
objProxy.age = 20

// ---------------------------------------------------
const infoProxy = reactive({
  address: 'gd'
})

watchFn(function () {
  console.log(The value of '-----info.address: ' + infoProxy.address)
})

infoProxy.address = 'bj'
Copy the code