JS advanced – vue2 and vue3 responsive principle

1. Object monitoring method

1.1 Listener object in vue2: Object.defineProperty(obj,key,descriptor)

Object.defineProperty is used in vue2 to monitor objects:

//Define an object first
const obj = {
  age: 16,
  height: 1.88,
  grade: 99
}

//Loop through the object and monitor each property
Object.keys(obj).forEach(key => {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    // Operate by accessing attribute descriptors
    get() {
      console.log(`${key} property is accessed`);
      return value
    },
    set(newValue) {
      console.log(`${key} attribute is assigned`);
      if (value === newValue) return
      value = newValue
    }
  })
})

obj.age
obj.height = 2.0

But there are some disadvantages in this method: First, the original intention of Object.defineProperty is not to monitor all properties in an object; second, if we want to monitor richer operations, such as adding properties and deleting properties, then Object.defineProperty is powerless.

So how can we monitor objects correctly? In ES6, the proxy class is added. If we listen to the related operations of an object, then we can create a proxy object (proxy object) first, and then all operations on the object will be completed through the proxy object.

1.2 Listening object in vue3: const proxyObj = new Proxy(target, handler)

In vue3, a proxy object is created through the Proxy class, and subsequent operations are directly performed on the proxy object instead of the original object:

//Define an object first
const obj = {
  name: 'why',
  age: 20,
  height:1.98
}

//Create proxy object
//parameter:
//target (listening object obj), key (property to be set), value (value of new property), receiver (calling proxy object objProxy)

const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(`The ${key} property of the object is accessed`, target);
    // return target[key]
    return Reflect. get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    if (target[key] !== value) {
      // target[key] = value
      Reflect.set(target,key,value,receiver)
    console.log(`The ${key} property of the object is set to a value`, target);
    }
  },
  has(target, key) {
    // return key in target
    return Reflect.has(target,key)
  },
  deleteProperty(target, key) {
    // delete target[key]
    Reflect.deleteProperty(target,key)
  }
})

console.log(objProxy.name);
console.log(objProxy.age);

objProxy.name = 'kobe'
objProxy.age = 20

console.log('name' in objProxy);
delete objProxy.height
console.log(objProxy);

The Reflect object is used in the above code. If you don’t know the Reflect object, click to understand Reflect

2. Implement responsive principles

2.1 Vue2 implements responsive

//1. Define a global function object to save dependent functions
let activeReactiveFn = null

//2. Define a listener function
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

//3. Define a class to add a corresponding dependency function to each property of the object, as well as an execution function
class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }
  //add dependent function
  addDepend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }
   //execute dependent function
  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

//4. Create a weakMap to store dependencies corresponding to multiple properties of multiple objects
const targetMap = new WeakMap()
function getDepend(target, key) {
  // The process of obtaining the map according to the target object
  let map = targetMap. get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  // Get the depend object according to the key
  let depend = map. get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depends
}

//Note: The main difference between vue2 and vue3 listening objects
// 5. Monitor the object and return the monitored object
function reactive(obj) {
  Object.keys(obj).forEach(key => {
    const value = obj[key]
    Object.defineProperty(obj, key, {
      get() {
        const depend = getDepend(obj, key)
        depend. addDepend()
        return value
      },
      set(newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend. notify()
      }
    })
  })
  return obj
}

//6, define an object
const obj = {
  name: "why",
  age: 18
}
// 7. Call the function to monitor the object
reactive(obj)

//8. The function triggered when the properties of the collection object are monitored
watchFn(() => {
  console.log(obj.name, 'monitoring object obj's name property');
})
watchFn(() => {
  console.log(obj.age, 'monitor the age attribute of obj object');
})

//9, operate on the object
obj.name = 'code'
obj.age = 22

2.2 Vue3 implements responsive

//1. Define a global function object to save dependent functions
let activeReactiveFn = null

//2. Define a listener function
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

//3. Define a class to add a corresponding dependency function to each property of the object, as well as an execution function
class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }
  //add dependent function
  addDepend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }
   //execute dependent function
  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

//4. Create a weakMap to store dependencies corresponding to multiple properties of multiple objects
const targetMap = new WeakMap()
function getDepend(target, key) {
  // The process of obtaining the map according to the target object
  let map = targetMap. get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  // Get the depend object according to the key
  let depend = map. get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depends
}

//Note: The main difference between vue2 and vue3 listening objects
// 5. Create proxy object function
//parameter:
//target (listening object obj), key (property to be set), value (value of new property), receiver (calling proxy object objProxy)
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // Get the corresponding depend according to target.key
      const depend = getDepend(target, key)
      // Add a response function to the depend object
      depend. addDepend()
      return Reflect. get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver)
      const depend = getDepend(target, key)
      depend. notify()
    }
  })
}

//6, define an object
const obj = {
  name: "why",
  age: 18
}
// 7. Create a proxy object through a function
const objProxy = reactive(obj)

//8. The function triggered when listening to the properties of the proxy object
watchFn(() => {
  console.log(objProxy.name, 'monitor the name attribute of the proxy object objProxy');
})
watchFn(() => {
  console.log(objProxy.age, 'monitor the age attribute of the proxy object objProxy');
})

//9, operate on the proxy object
objProxy.name = 'code'
objProxy.age = 22

In the above code, the newly added data structures in ES6 such as Set(), WeakMap(), and Map() are used. Click on them to learn more details.

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge