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:
- Get/set properties are functions and are called getters/setters by convention (Java, c++)
The value or writable
和Get or set
Cannot occur at the same time, otherwise an error is reported- 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:
- 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. - 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.