This is the third day of my participation in Gwen Challenge

This article is a study note about the principle of data responsiveness, the purpose is to better understand the underlying principle of Vue, the length is long, so it is divided into several chapters, will be updated in the future

Object.defineProperty()

Thanks to the features of Object.defineProperty(), vue’s data changes are non-invasive, unlike React and applets. Details can be found in the MDN documentation, with several points in particular:

  1. Get/set properties are functions and are called getters/setters by convention (Java, c++)
  2. The value or writableGet or setCannot occur at the same time, otherwise an error is reported
  3. Notice the difference between Object.defineproperties ()

Define the defineReactive function

Object.defineproperty (), when using getters and setters, requires a variable rotation to modify the property, as shown in value, which is cumbersome.

const obj = {}
let value
Object.defineProperty(obj, 'a', {
  enumerable: true.configurable: true.get() {
    console.log('getter')
    return value
  },
  set(newValue) {
    value = newValue
    console.log('setter', newValue)
  }
})
Copy the code

So defineReactive is defined to add a reactive attribute to the object. Here we create a closure environment: a closure must have both an inside and outside function, and the value of the outside function defineReactive forms the closure.

const obj = {}
function defineReactive(data, key, value) {
  // If only two parameters are passed, set value to data[key]
  if (arguments.length === 2) value = data[key]
  
  Object.defineProperty(data, key, {
    enumerable: true.// can be enumerated (for... In or object.keys method)
    configurable: true.// Can be configured, such as delete
    get() {
      console.log('Checked' + key + 'properties')
      return value
    },
    set(newValue) {
      console.log('modified' + key + 'properties')
      value = newValue
    }
  })
}

defineReactive(obj, 'a'.10)
console.log(obj.a)
obj.a = 11
console.log(obj.a)
Copy the code

The result is shown below

Recursively detects all properties of an object

Create index.js as the main entry file to test the effect. Let obj as the main entry file. The objective is to view and modify all obj properties by passing obj as an argument to the observe function.

// index.js
import observe from './observe.js'
let obj = {
  a: {
    m: {
      n: 1}},b: 2
}
observe(obj)
Copy the code

Process analysis

Observe function

The observe function is used to observe whether the attributes of an object (value) have been monitored (has an __ob__ attribute), and if not, to make the attributes responsive (via the New Observer(value)). Note: The odd variable name __ob__ is given to ensure that it does not have the same name as the object’s original property.

// observe.js
import Observer from './Observer.js'
export default (value) => {
  if (typeofvalue ! = ='object') return
  if(value.__ob__ ! = =undefined) {
    // leave blank for now
  } else {
    new Observer(value)
  }
}
Copy the code

The Observer class

An Observer is a class that, once new, does two things:

  1. Add to the value (which is actually an object) passed in__ob__Property with the value of this instance of new (that is, this in the constructor), as desired__ob__Properties are not enumerable, so def is used to handle them.
  2. Iterate over the attributes of value and make them reactive via defineReactive
// Observer.js
import { def } from './utils.js'
import defineReactive from './defineReactive.js'

export default class Observer {
  constructor(value) {
    def(value, '__ob__'.this.false)
    this.walk(value)
  }
  // Process the object to make its properties responsive
  walk(value) {
    for (let key in value) {
      defineReactive(value, key)
    }
  }
}
Copy the code

Def function is defined as follows

export const def = (obj, key, value, enumerable) = > {
  Object.defineProperty(obj, key, {
    value,
    enumerable,
    writable: true.configurable: true})}Copy the code

Refine the defineReactive function

Add observe(value) in two places compared to the previous definition, thus realizing all properties of the recursively detected object. If the value is an object that needs to be detected, observe it too.

// defineReactive.js
import observe from './observe.js'

export default function defineReactive(data, key, value) {
  if (arguments.length === 2) value = data[key]
  
  // Note that the key is not passed but the value, because the key is just a string and the value is the object to which the key points
  observe(value)
  
  // Change the key attribute of data to a responsive attribute
  Object.defineProperty(data, key, {
    enumerable: true.configurable: true.get() {
      console.log('Checked' + key + 'properties')
      return value
    },
    set(newValue) {
      console.log('modified' + key + 'properties')
      value = newValue
      // Modified attributes also need to be observed, if the object needs to be detected
      observe(newValue)
    }
  })
}
Copy the code

At this point, each attribute passed to Observe’s obj in index.js is responsive

// index.js. Omit the previous code obj.a.m = {y: 8
}
console.log(obj.a.m.y)
Copy the code

The test results are as follows

The following is about reactive processing of arrays, but I will continue to share them in the next article so as not to make each article too long and drowsy to read

One More Thing

Ordinary objects also have getters and setters:

  • Get propertyName(){} the callback function used to get the current property value
  • Set propertyName(){} a callback function that monitors changes in the value of the current property
  • In the following code, attribute A is called a “data attribute” and has only one simple value; Property B Properties defined with getter and setter methods are called accessor properties.
var num= {
    a: 2.get b() {return 2}}Copy the code

An accessor property is defined as one or two functions with the same name as the property. This function definition does not use the function keyword, but instead uses get or set, and does not use a colon to separate the property name from the function body.