Explain in detail the principle of data response to realize vue

  • 2021-10-24 18:55:26
  • OfStack

This article is mainly for friends who don't know or have not been exposed to vue responsive source code. Its main purpose is to have a basic understanding of vue responsive principle. If asked such questions in the interview, you can know what the interviewer wants you to answer. [PS: If there is anything wrong in the article, please correct it.]

Understanding of response formula

Responsive, as its name implies, is a change in data, which will cause the view to be updated. This article mainly analyzes the implementation of object and array reactive principle in vue 2.0, and we will leave it for the next article to analyze dependency collection and view update.

In vue, when we talk about responsive data, 1 generally refers to data of array type and object type. vue hijacks the attributes of objects by Object. defineProperty method, and arrays are realized by overriding arrays. Let's simply implement 1.

First, we define a data that needs to be intercepted


const vm = new Vue({
 data () {
  return {
   count: 0,
   person: { name: 'xxx' },
   arr: [1, 2, 3]
  }
 }
})
let arrayMethods
function Vue (options) { //  Here only consider the right  data  Operation of data 
 let data = options.data
 if (data) {
  data = this._data = typeof data === 'function' ? data.call(this) : data
 }
 observer (data)
}
function observer(data) { 
 if (typeof data !== 'object' || data === null) {
  return data
 }
 if (data.__ob__) { //  Existence  __ob__  Property, indicating that it has been intercepted 
  return data
 }
 new Observer(data)
}

Please continue to look at the implementation and function of arrayMethods, Observer and __ob__ here

Implement the Observer class


class Observer {
 constructor (data) {
  Object.defineProperty(data, '__ob__', { //  In  data  Definition above  __ob__  Attribute, which is needed in array hijacking 
   enumerable: false, //  Innumerable 
   configurable: false, //  Not configurable 
   value: this //  Value is  Observer  Instances 
  })
  if (Array.isArray(data)) { //  Intercept an array 
   data.__proto__ = arrayMethods //  Prototype inheritance 
   this.observerArray(data)
  } else { //  Object to intercept 
   this.walk(data)
  }
 }
 walk (data) {
  const keys = Object.keys(data)
  for(let i = 0; i < keys.length; i++) {
   const key = keys[i]
   defineReactive(data, key, data[key])
  }
 }
 observerArray (data) { //  Intercept every 1 Items 
  data.forEach(value => observer(value))
 }
}

Object interception

Several points to be paid attention to in hijacking objects:

Traverse the object. If the value is still an object type, you need to call the observer observation method again If the new value set is an object type, it also needs to be intercepted

//  Handle interception of objects 
function defineReactive(data, key, value) {
 observer(value) //  If  value  Value is still an object type, requiring recursive hijacking 
 Object.defineProperty(data, key, {
  get() {
   return value
  },
  set(newValue){
   if (newValue === value) return
   value = newValue
   observer(newValue) //  If you set the  newValue  Value is also an object type and needs to be hijacked 
  }
 })
}

Hijacking of arrays

Several points to pay attention to when hijacking arrays:

Arrays use the idea of function hijacking (slicing programming) to intercept data The newly added value in the array needs to be intercepted again if it is an object type

const oldArrayPrototype = Array.prototype
arrayMethods = Object.create(oldArrayPrototype)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] //  Methods that can change the original array 
methods.forEach(method => {
 arrayMethods[methods] = function (...args) {
  const result = oldArrayPrototype[methods].call(this, ...args)
  const ob = this.__ob__ // this  Is the array that calls the modification method 
  let inserted; //  The collection of new items in the array, which needs to be intercepted again 
  switch(methods) {
   case 'push': 
   case 'unshift':
    inserted = args
   case 'splice':
    inserted = args.slice(2) //  Because  splice  No. 1 2 The following parameters are added 
  }
  if (inserted) {
   ob.observerArray(inserted)
  }
  return result
 }
})

Principle summary

In the interview, if we need to write the responsive principle of vue by hand, the above code is enough. But we learn the source code of vue, if in the interview can give the following to sum up the answer can get the favor of the interviewer.

Responsive principle of vue 2.0 source code:

Because objects are intercepted recursively, the deeper the data hierarchy, the worse the performance Arrays are not intercepted using Object. defineProperty because performance is poor if there are too many array items Only the data defined in data will be intercepted, and the attributes added by vm. newObj = 'xxx' on the instance will not be intercepted later Changing the index and length of the array will not be intercepted, so it will not cause the view to be updated If you need to intercept new attributes on data and change the index and length of the array, you can use the $set method You can use the Object. freeze method to optimize data and improve performance without overriding the set and get methods

vue 3.0 source code responsive principle:

In version 3.0, proxy is used instead of Object. defineProperty, which has 13 interception methods, and does not need to process objects and arrays separately, nor does it need to intercept recursively, which is also the place where its performance is improved the most Simple Implementation of Responsive Principle in vue 3.0

const handler = {
 get (target, key) {
  if (typeof target[key] === 'object' && target[key] !== null) {
   return new Proxy(target[key], handler)
  }
  return Reflect.get(target, key)
 },
 set (target, key, value) {
  if(key === 'length') return true
  console.log('update')
  return Reflect.set(target, key, value)
 }
}
const obj = {
 arr: [1, 2, 3],
 count: { num: 1 }
}
// obj  Is the target object of the proxy,  handler  Is a configuration object 
const proxy = new Proxy(obj, handler)

Related articles: