When it comes to responsiveness, I’m sure you all know that. In vue’s case, all we need to do is register a property in the data function, and when that property changes, it triggers dom rendering. So how to achieve this process, we will analyze from the bottom and the implementation principle.
A defineProperty.
- First, vue2.0 uses the object.defineproperty method to implement responsiveness. Let’s look at the arguments to the object.defineProperty method
Object.defineProperty(obj, prop, descriptor)
Copy the code
parameter
-obj: specifies the object on which properties are to be defined. -prop: specifies the name of the property to be defined or modified. -descriptor: The obj represents the object that you want to work with, and the prop is a key descriptor for the properties that you want to define or modify. The keydescriptor is a different object, and is freely defined or modified. Enumerable: Boolean Specifies whether the default property can be modified. Writable: Boolean specifies whether the value of this property can be modified. Set type: Function meaning: A decorated property is executed when it is accessedCopy the code
-
Because today we’re going to focus on the principles of response, let’s focus on the get and set methods in Descriptor:
When we assign a value, we don’t have to do anything special to trigger dom rendering, or other incidental operations. Similar to listening for a value, when it changes, we perform a function. So it comes to mind that there are a number of ways to implement this logic. For example, we first thought of using an endless loop or setInterval to listen for changes in data. Of course, the principle is understandable, but the performance of this is very poor. And there will be a lot of problems. Is there a better way to address this need? DefineProperty provides a set method, as you will notice. Is this the way to implement responses? The answer is right!
-
Let’s see how defineProperty executes get and set on access and assignment:
const obj = {}
Object.defineProperty(obj, 'value', {
get() {
console.log('get value')},set(newVal) {
console.log('set value', newVal)
}
})
obj.value
console.log(obj.value)
obj.value = 1
Copy the code
- What do we get when we run the above code? First, obj. Value executes console.log(‘get value’); And then we call obj.value; Console. log(‘get value’) is executed again; Then run console.log(undefined); Finally, run console.log(‘set value’, 1)
This is what I got when I ran it on the Chrom console.
You think that’s it? That’s it? What if you add a line after console.log(obj.value) and print obj.value again? You think it’s going to print a ‘get value’ and a 1?
Let’s look at the results:
Yi? Why undefined? We print obj findsObj. value = 1 So why?
- Let’s do another example and you’ll see
const obj = {}
Object.defineProperty(obj, 'value', {
get() {
console.log('get value')
return 1
},
set(newVal) {
console.log('set value', newVal)
}
})
obj.value
console.log(obj.value) / / 1
obj.value = 2
console.log(obj.value) / / 1
obj.value = 3
console.log(obj.value) / / 1 `
Copy the code
If you’re careful, it’s not that you didn’t assign a value, it’s that the outside get doesn’t set a return value, so the get method always returns undefined, so there’s no illusion that you didn’t assign a value. The value actually goes in, but when you access it, you go to the get method, and get returns undefined. After adding a return value to the get method outside of the above code, accessing the property will always return the value you set.
So how do we get get to return the correct value? We use a variable to receive the changed value, and then return it in the get method.
let _value
const obj = {}
Object.defineProperty(obj, 'value', {
get() {
return _value
},
set(newVal) {
_value = newVal
}
})
obj.value
console.log(obj.value) // undefined
obj.value = 2
console.log(obj.value) / / 2
obj.value = 3
console.log(obj.value) / / 3
obj.xxxxx = 3 // Do not execute set because the attribute XXXXX is not registered
console.log(obj.xxxxx) // Do not execute get because the attribute XXXXX is not registered
Copy the code
Okay? That completes our function perfectly. Going back to responsiveness, all we need to do is register an attribute, and then execute the render method on the set of that attribute, so whenever that attribute is assigned it will be rerendered.
Yeah, it’s going to execute set whenever it’s assigned even if it’s the same assignment as the last one. So to improve performance, we also need to do diff calculations in Render.
- Let’s write a method that registers a set of responsive data and executes the render function whenever each item in the set changes:
const render = () = > {
console.log('rendering')}const defineReactive = (obj, key, val) = > {
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (val === newVal) { / / to imitate the diff
return
}
// Assign the new value to the old value
val = newVal;
// Execute the render function
render()
}
})
}
const reactive = (obj) = > {
for (const key inobj) { defineReactive(obj, key, obj[key]); }}const data = {
a: 1.b: 2.c: 3
}
reactive(data)
data.a = 5 // Print the render
data.b = 7 // Print the render
data.c = 3 // Do not print because the value has not changed
Copy the code
All right? But as you’ll see, it’s a very simple function. First of all, what if objects are nested? So let’s recurse him
5. Recursion of reactive methods
const render = () = > {
console.log('rendering')}const defineReactive = (obj, key, val) = > {
reactive(val) // We recurse here
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (val === newVal) { / / to imitate the diff
return
}
// Assign the new value to the old value
val = newVal;
// Execute the render function
render()
}
})
}
const reactive = (obj) = > {
if (typeof obj === 'object') { // We need to add a condition that the recursion ends
for (const key inobj) { defineReactive(obj, key, obj[key]); }}}const data = {
a: 1.b: 2.c: {
c1: {
af: 999
},
c2: 4
}
}
reactive(data)
data.a = 5 / / rendering
data.b = 7 / / rendering
data.c.c2 = 4 / / not rendering
data.c.c1.af = 121 / / rendering
Copy the code
If you look at Vue, you’ll see that in addition to all this, vue is also responsive to changes in the array. Vue takes the Array prototype, injects render logic into the usual method, and then assigns it to the Array prototype. This way, when you use the array method, it will trigger render. To make it easier for you to understand, I’ll do a simple example here
- The response of an array
const render = () = > {
console.log('rendering')}const arrPrototype = Array.prototype // Save the array prototype
cosnt newArrProtoType = Object.create(arrPrototype) // Create a new array prototype
['push'.'prop'.'shift'.'unshift'.'sort'.'splice'.'reverse'].forEach(methodName= > {
newPropType[methodName] = function () { / / to modify the prototype specified this a few changes in the array, the method of function the same, just in the revised render function
oldPropType[method].call(this. arguments);// Inject renderingrender(); }})const reactive = (obj) = > {
if (Array.isArray(obj)) { // If it is an array
obj.__proto__ = newArrProtoType // Assign the newly defined prototype object to the array's proto, so that the array will handle rendering while executing those methods}}const data = [1.2.3.4]
reactive(data)
data.push(5) / / rendering
data.splice(0.2) / / rendering
Copy the code
Array and object responses can be used in combination with the above two.
-
When we use VUE2, sometimes adding properties to objects and removing properties does not trigger rendering. Now that I’ve seen the above, I think it’s pretty clear why it doesn’t trigger the render. Because of these two operations, you can’t get the set to execute at all. So Vue provides set and set and set and DELETE methods. Used to manually trigger rendering.
-
React and vue
When it comes to manually triggering renderings, react is a must. And you’ll see, there’s no reactive in React. All renderings require you to perform setState before they are rendered. In fact, the vue can be compared to the automatic transmission of a car, and react to the manual transmission. Vue only needs to change the data when it needs to update the model, just like you fill the gas door, and the automatic transmission will give you the clutch and shift action automatically. React, on the other hand, requires your own actions to trigger the render, which means you need to clutch and gear yourself. In contrast, the advantages and disadvantages are similar to manual and automatic transmission. Manual gear plays well, is very fuel efficient, and can maximize the performance of the car. For example, when you need to accelerate performance, you can operate the gear shift at high speed, while automatic transmission, you need to use pre-set modes, such as sports, economy, etc. The timing of their shift is already defined. It’s more suitable for beginners. So do react. Play well, reasonable use of rendering time, and reasonable allocation of data resources. Guarantee performance. Vue doesn’t have to think that much. However, the novice can increase memory consumption and reduce performance by defining too much useless responsive data.
The proxy.
- The use of proxy
const obj = new Proxy(target, handler)
Copy the code
parameter
-target: specifies the type of object to be listened on: objects, arrays, functions, Proxy objects. Callback method collection type: Object, Callback methods of collection - handler. GetPrototypeOf () - handler. SetPrototypeOf () - handler. IsExtensible () - handler. PreventExtensions () handler.getOwnPropertyDescriptor() - handler.defineProperty() - handler.has() - handler.get(target, property) - handler.set(target, property, value) - handler.deleteProperty() - handler.ownKeys() - handler.apply() - handler.construct()Copy the code
- Naturally, we found that there are also get and set methods. Compared to defineProperty, proxy receives target of any type, including a native array, a function, or even another proxy object. With this, we can see that implementing a responsive object is much less of a hassle. So let’s first look at how a Proxy listens for data changes using sets and gets. As for other methods, they are not the focus of this lecture, and students who are interested can study them by themselves
const obj = {
a: 1.b: { a1: 32.b1: { a2: 31}}}const handler = {
get(obj, prop) {
console.log('get', obj[prop])
return obj[prop] / / return obj [prop]
},
set(obj, prop, value) {
console.log('set', prop)
obj[prop] = value
return true // Set must return true to indicate completion of assignment; otherwise, an error will be reported}}const p = new Proxy(obj, handler);
console.log(p.a) // Print the value of get p.a
console.log(p.b.a1) // Print the value of p.B.a1
console.log(p.b.b1.a2) // Print the value of p.B.a1
Copy the code
We can see from the above code that we can listen for the access to the property by listening for the nested property
Proxy(obj, handler);
const obj = {
a: 1.b: { a1: 32.b1: { a2: 31}}}const handler = {
get(obj, prop) {
console.log('get', obj[prop])
return obj[prop] / / return obj [prop]
},
set(obj, prop, value) {
console.log('set', prop)
obj[prop] = value
return true // Set must return true to indicate completion of assignment; otherwise, an error will be reported}}const p = new Proxy(obj, handler);
p.a = 2 / / print the set
p.a.a1 = 12 / / don't trigger
Copy the code
- As we can see from the above, set is not inherently recursive like GET, so if we want to make it reactive, we need to make it reactive again for nested objects (or arrays, again). We can do this:
const obj = {
a: 1.b: { a1: 32.b1: { a2: 31}}}const handler = {
get(obj, prop) {
const val = obj[prop]
if(val ! = =null && typeof val=== 'object') {return new Proxy(val, handler);// Agent inner layer
}else{
return val; / / return obj [prop]}},set(obj, prop, value) {
console.log('set', prop)
obj[prop] = value
return true // Set must return true to indicate completion of assignment; otherwise, an error will be reported}}const p = new Proxy(obj, handler);
p.a = 5 / / print the set
p.b.a1 = 10 / / print the set
p.b.b1.a3 = 2 // Print set (add new properties)
delete p.b.b1.a3 // Sets that do not execute proxy cannot be deleted
Copy the code
We find that we can listen to everything in the set except delete, but what if we need to listen to delete? This requires the deleteProperty method 5, which is the same as set. Let’s look at an example
const obj = {
a: 1.b: { a1: 32.b1: { a2: 31}}}const handler = {
get(obj, prop) {
const val = obj[prop]
if(val ! = =null && typeof val=== 'object') {return new Proxy(val, handler);// Agent inner layer
}else{
return val; / / return obj [prop]}},set(obj, prop, value) {
console.log('set', prop)
obj[prop] = value
return true // Set must return true to indicate completion of assignment; otherwise, an error will be reported
},
deleteProperty(obj: any, prop: any,) { // Let's add a delete callback
console.log('del', prop);
delete obj[prop];
return true; // As set, return true to indicate deletion is complete}}const p = new Proxy(obj, handler);
p.b = 3 / / print the set
delete p.a / / print del
Copy the code
- Let’s try the array again, see if Proxy works
const obj = [1.2, { a: 1 }]
const handler = {
get(obj, prop) {
const val = obj[prop]
if(val ! = =null && typeof val=== 'object') {return new Proxy(val, handler);// Agent inner layer
}else{
return val; / / return obj [prop]}},set(obj, prop, value) {
console.log('set', prop)
obj[prop] = value
return true // Set must return true to indicate completion of assignment; otherwise, an error will be reported}}const p = new Proxy(obj, handler);
p[0] = 5 / / print the set
p.push(4) / / print the set
p[2].a = 10 // Print set (modifies the values of the objects in the array)
p[2].b = 12 // Print set (add properties to the objects in the array)
Copy the code
-
We will find that Proxy is much simpler and more powerful than defineProperty. So instead of using the reactive method above, we’re going to write a reactive method using a Proxy and we’re not going to do it here. You can try it if you are interested.
-
Compare the two and summarize the differences;
- Proxy is a Proxy for the entire Object, whereas object.defineProperty can only Proxy a property. So when we write a responsive function, defineProperty needs to add a listener for each attribute
- Object. DefineProperty cannot be detected by Proxy.
- Object. DefineProperty cannot be listened to.
- If all the internal properties of an Object need to be recursed, Proxy can recurse only when called, whereas object.definePropery needs to recurse all at once, which is worse than Proxy. So we can compare two recursions here, definePropery, which is at the beginning, we recurse all the properties of the object that we pass in, including the unfamiliar inside. And then I’m going to do set get. But the recursion of the Proxy is in the set, so we can adjust the recursion principle according to the requirements, that is, under certain conditions, so that it doesn’t recurse. Let’s take a very simple example. We need to render an object on the page, and this object will always be reassigned as a whole. Properties are not modified individually. We can then use Proxy control to prevent recursion of this object, thus improving performance
- Proxy is not compatible with IE, and Object. DefineProperty is not compatible with IE8 or later
- Proxy is more convenient to use than Object. DefineProperty.