When it comes to the principle of VUE, the most important thing is responsiveness, virtual DOM and DIff algorithm, template compilation. Today, we will go into the responsiveness of VUE, discuss the implementation principle and deficiency of VUe2. X responsiveness, and how to rewrite the implementation scheme of responsiveness in VUe3.0 version.
1. What is reactive
Vue is an MVVM framework. The core of MVVM is data-driven view. Generally speaking, users do not directly manipulate DOM, but manipulate data. Similarly, user actions (events) on the view change the data in turn. Reactive, on the other hand, is the first step in implementing data-driven views, that is, listening for changes in data, so that when users set data, they can notify vUE internally for view updates, for example
<template> <div> <div> {{name}} </div> < button@click ="changeName"> </button> </div> </template> <script> export default { data () { return { name: 'A' } }, methods: { changeName () { this.name = 'B' } } } </script>Copy the code
In the code above, after clicking the button, the name property will change, and the page displayed A will change to B
2. vue2.x
Implementing responsiveness
2.1 Core API — Object.defineProperty()
I think most of you know vUE at some point or another. The core of vUE responsiveness is Object.defineProperty(). Here’s a quick review
const data = {}
let name = 'A'
Object.defineProperty(data, 'name', {
get () {
return name
},
set (val) {
name = val
}
})
console.log(data.name) // get()
data.name = 'B' // set()
Copy the code
As we can see in the above code, object.defineProperty () is used to define a property (method) for an Object and provide two internal implementations of set and get to get or set the property (method).
2.2 How to achieve responsiveness
First, we define an initial data as follows
const data = {
name: 'A'.age: 18.isStudent: true.gender: 'male'.girlFriend: {
name: 'B'.age: 'the'.isStudent: true.gender: 'female'.parents: {
mother: {
name: 'C'.age: '44'.isStudent: false.gender: 'female'
},
father: {
name: 'D'.age: '46'.isStudent: false.gender: 'male'}}, hobbies: ['basketball'.'one-piece'.'football'.'hiking']}Copy the code
We also define a method to render the view
function renderView () {
// Render the view when the data changes
}
Copy the code
And a core method that implements responsiveness. This method takes three parameters: target is the data object itself, key and value are the key of the object and its corresponding value
function bindReactive (target, key, value) {}Copy the code
Finally, we define entry methods that implement responsiveness
function reactive() {/ /... }Copy the code
Our final call is theta
const reactiveData = reactive(data)
Copy the code
2.2.1 For primitive types and objects
The above data, we simulated a simple information introduction of a person, you can see the object of the character values of strings, numbers, booleans, objects, arrays. For primitive types like strings, numbers, booleans, we just return them
function reactive (target) {
// First, the object is not returned directly
if (typeoftarget ! = ='object' || target === null) {
return target
}
}
const reactiveData = reactive(data)
Copy the code
If the field value is a reference type like an Object, we need to traverse the Object and set object.defineProperty () to each key value of the Object separately. Note that this procedure needs to be called recursively, because objects can be nested in multiple layers as shown in the data we present. We define a function, bindReactive, to describe the process of listening on a reactive object
function bindReactive (target, key, value) {
Object.defineProperty(target, key, {
get () {
return value
},
set (val) {
value = val
// Triggers a view update
renderView()
}
})
}
function reactive (target) {
// First, the object is not returned directly
if (typeoftarget ! = ='object' || target === null) {
return target
}
// Iterate over the object, listening for each key responsively
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
Copy the code
Considering recursion, we need to recursively call Reactive to listen for object properties when executing the core method bindReactive, and also recursively call reactive to update data when setting (updating) data, so our core method bindReactive changes to
function bindReactive (target, key, value) {
reactive(value)
Object.defineProperty(target, key, {
get () {
return value
},
set (val) {
reactive(val)
value = val
// Triggers a view update
renderView()
}
})
}
function reactive (target) {
// First, the object is not returned directly
if (typeoftarget ! = ='object' || target === null) {
return target
}
// Iterate over the object, listening for each key responsively
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
Copy the code
The above code can be optimized so that when a set is set, if the new value is the same as the previous value, it does not trigger a view update, so our method becomes
function bindReactive (target, key, value) {
reactive(value)
Object.defineProperty(target, key, {
get () {
return value
},
set (newVal) {
if(newVal ! == value) { reactive(newVal) value = newVal// Triggers a view update
renderView()
}
}
})
}
function reactive (target) {
// First, the object is not returned directly
if (typeoftarget ! = ='object' || target === null) {
return target
}
// Iterate over the object, listening for each key responsively
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
Copy the code
So far, we’ve implemented responsive listening for primitive types and objects, and when the data changes, we call the renderView method (which can do anything) to update the view after the data has been updated.
2.2.2 For arrays
Clearly, although Object.defineProperty() does a good job of responding to primitive types and normal objects, this method does nothing for arrays. So how does VUE implement responsive listening on arrays? Let’s first go back to the official vue documentation
As you can see, vUE can respond to array changes when executing push, POP, shift, unshift, etc., triggering view updates.
But as we all know, the array native methods don’t have the ability to update the view in response, so we know that Vue must have overwritten the array methods, so now the question is how do arrays implement responsiveness instead of how do arrays rewrite the array API.
Create (prototype); this method creates an Object whose prototype points to the prototype parameter. Then we can rewrite these array methods as well:
// Array prototype
const prototype = Array.prototype
// create a newPrototype object whose prototype is an array prototype (hence all array apis on newPrototype)
const newPrototype = Object.create(prototype)
const methods = ['push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse']
methods.forEach(method= > {
newPrototype[method] = (a)= > {
prototype[method].call(this. args)// View update
renderView()
}
})
Copy the code
To implement array responsiveness, we’re refining the entry method reactive
function bindReactive (target, key, value) {
reactive(value)
Object.defineProperty(target, key, {
get () {
return value
},
set (newVal) {
if(newVal ! == value) { reactive(newVal) value = newVal// Triggers a view update
renderView()
}
}
})
}
function reactive (target) {
// First, the object is not returned directly
if (typeoftarget ! = ='object' || target === null) {
return target
}
// For arrays, prototype modification
if (Array.isArray(target)) {
target.__proto__ = newPrototype
}
// Iterate over the object, listening for each key responsively
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
Copy the code
So far, we have explained the responsivity principle of the VUe2. X version
2.3 Disadvantages of vue2. X version responsive implementation scheme
Through our analysis, we can also see the disadvantages of vue2. X version reactive implementation:
Object.defineProperty()
The API does not natively listen on arrays responsively- For deeply nested data, recursion costs a lot of performance
- We noticed that,
Object.defineProperty()
The problem with this implementation, and with arrays, is that there is no way to listen for subsequent manual additions and delets of attribute elements, such as arrays. Setting and changing values directly through indexes does not trigger view updates, as VUE doesvue.set
andvue.delete
suchapi
But it is inconvenient after all
3. vue3.0
Implementing responsiveness
Not long ago, Vue3.0 was officially released. Although there is no official promotion yet, some changes in it are worth our attention and learning
3.1 Proxy
andReflect
Because of the problems with the reactive implementation in vue2.x, vue officially rewrote the reactive implementation completely in vue 3.0, using Proxy and Reflect instead of Object.defineProperty().
3.1.1 Proxy
First, let’s look at the definition of Proxy by MDN:
The Proxy object is used to define custom behavior for fundamental operations(e.g. property lookup, assignment, enumeration, function invocation, etc).
Copy the code
Proxy objects are used to define custom behavior for basic operations (such as lookup, assignment, enumeration, function call, etc.).
let proxy = new Proxy(target, handler)
Copy the code
Note that target can be a native array.
target
:Proxy
The wrapped target object (which can be any type of object, includingThe original array
, function, or even another proxy).handler
: an object whose properties are functions that define the behavior of the agent when an operation is performed.
Here’s an example:
let handler = {
get: function(target, name){
return name in target ? target[name] : 'sorry, not found'; }};let p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 'sorry, not found'
Copy the code
3.1.2 Reflect
Let’s start with MDN’s definition of Reflect:
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it's not constructible.
Copy the code
Reflect is a built-in object that provides methods for intercepting JavaScript operations. These methods are the same as proxy handlers. Reflect is not a function object, so it is not constructible.
The Refelct object provides many methods. Here are just a few of the common methods used to implement the reactive style:
Reflect.get()
: Gets the value of an attribute on an object, similar totarget[name]
.Reflect.set()
: A function that assigns values to attributes. Returns aBoolean
, if the update succeededtrue
.Reflect.has()
: Determines whether an object has an attribute, andin
Operators do exactly the same thing.Reflect.deleteProperty()
: is the delete operator of the function, equivalent to the delete target[name].
Therefore, we can combine Proxy and Reflect to perform responsive listening
3.2 Proxy
andReflect
Implementing responsiveness
Here is the code directly posted to modify the method we implemented earlier:
function bindReactive (target) {
if (typeoftarget ! = ='object' || target == null) {
// If it is not an object or array, return it directly
return target
}
// Since Proxy natively supports arrays, there is no need to implement it yourself
// if (Array.isArray(target)) {
// target.__proto__ = newPrototype
// }
// Pass to Proxy handler
const handler = {
get(target, key) {
const reflect = Reflect.get(target, key)
// When we get an object attribute, Proxy only recurses to the acquired level, not to the sublevels
return bindReactive(reflect)
},
set(target, key, val) {
// Duplicate data is not processed
if (val === target[key]) {
return true
}
// We can do different things with the existing key
if (Reflect.has(key)) {
} else{}const success = Reflect.set(target, key, val)
// Whether the setting succeeds
return success
},
deleteProperty(target, key) {
const success = Reflect.deleteProperty(target, key)
// Whether the deletion succeeds
return success
}
}
// Generate a proxy object
const proxy = new Proxy(target, handler)
return proxy
}
// Implement data responsive listening
const reactiveData = bindReactive(data)
Copy the code
In the above code, we can see that the problems existing in the vue2. X response have been solved well:
Proxy
Supports listening on native arraysProxy
Will only recurse to the level needed to fetch data, will not continue to recurseProxy
You can listen for manual addition and deletion of data
Is vue3.0’s responsive solution perfect? The answer is no, mainly because Proxy and Reflect have browser compatibility issues and cannot be polyfilled.
4. To summarize
In this paper, a detailed and in-depth analysis of the vUE responsive principle, for 2.x and 3.0 version of the implementation of the difference, each has advantages and disadvantages, no scheme is perfect, I believe that in the future, when the browser compatibility problem is less and less, life will be better!