preface
The third question of this set of advanced front-end Vue interview questions is how to detect array changes in Vue. If you are not familiar with this question, please learn together.

In the previous article, we mentioned vue2 0 and 3.0, but there is no in-depth discussion. This paper will conduct an in-depth analysis on how Vue detects the value changes of various data types in versions 2.0 and 3.0, so as to achieve page responsiveness, and find out why the changes of array types need special treatment. Finally, Vue will be changed from 2.0 to 3.0 X upgrade to 3 The reason why different data monitoring principles are used in the process of X is also explored.
Start with a piece of basic code
The following code is very simple. Students who have written Vue can understand what it is doing, but can you accurately say what changes this code has on the first second, second second and third second pages?
<!DOCTYPE html> <html> <script src="https://unpkg.com/vue/dist/vue.js"></script> <body> <div id="app"> <div>{{ list }}</div> </div> <script> new Vue({ el: '#app', data: { list: [], }, mounted() { setTimeout(()=>{ this.list[0] = 3 }, 1000) setTimeout(()=>{ this.list.length = 5 }, 2000) setTimeout(()=>{ this.$set(this.list, this.list) }, 3000) } }) </script> </body> </html>
You'd better copy the above code by hand, save the new HTML file locally, and then open it for debugging. I'll talk about the results directly here. When this code is executed, the page will not change in the first and second seconds, and will not change until the third second. Consider that the first and second seconds have changed the value of the list. Why does Vue's two-way binding fail here? Around this problem, let's take a step-by-step look at Vue's data change monitoring implementation mechanism.
Vue2. Data change monitoring for 0
Let's look at it from simple to deep. First, we need to listen to ordinary data types.
1. The detection attribute is the basic data type
Monitor common data types, that is, the value of the object attribute to be monitored is the change of five basic types of non objects. Here, we don't look at the source code directly. Each step is implemented manually to make it easier to understand.
<!DOCTYPE html> <html> <div> name: <input id="name" /> </div> </html> <script> // Listen to the name attribute under the Model. When the name attribute changes, the response of page id=name will change const model = { name: 'vue', }; // Using object Defineproperty creates a listener function observe(obj) { let val = obj.name; Object.defineProperty(obj, 'name', { get() { return val; }, set(newVal) { // When there is a new value setting, execute setter console.log(`name Change: from ${val}reach ${newVal}`); // Parse to page compile(newVal); val = newVal; } }) } // The parser responds to the changed data on the page function compile(val) { document.querySelector('#name').value = val; } // Call the listener and start listening to the model observe(model); </script>
Debug the process on the console.

When debugging the above code, I first checked the model After the initial value of name is reset, the setter function can be triggered and executed, so that the page can achieve a responsive effect.
However, after assigning the name attribute to the object type, insert a key1 attribute into the new object, and then change the value of key1. At this time, the page cannot be triggered in response.
Therefore, in the above implementation of observe, when the name is an ordinary data type, there is no problem listening, and when the content to be monitored is the change of the object, there is a problem with the above writing.
Let's take a look at how to implement the observe function, which monitors the object type attribute.
2. Detection attribute is object type
From the above example, when the detected attribute value is an object, it can not meet the monitoring requirements. Next, further transform the observe monitoring function. The solution is very simple. If it is an object, just change all common types of monitoring under the current object again. If there are object attributes under the object, just continue to monitor. If you are familiar with recursion, I'll know how to solve the problem right away.
<!DOCTYPE html> <html> <div> name: <input id="name" /> val: <input id="val" /> list: <input id="list" /> </div> </html> <script> // Listen to the name attribute under the Model. When the name attribute changes, the response of page id=name will change const model = { name: 'vue', data: { val: 1 }, list: [1] }; // Listener function function observe(obj) { // Traverse all attributes and listen separately Object.keys(obj).map(key => { // Special treatment of object attribute if (typeof obj[key] === 'object') { // Listening again for object properties observe(obj[key]); } else { // Listening for non object properties defineReactive(obj, key, obj[key]); } }) } // Using object Defineproperty is used to listen to object properties function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { return val; }, set(newVal) { // When there is a new value setting, execute setter console.log(`${key}Change: from ${val}reach ${newVal}`); if (Array.isArray(obj)) { document.querySelector(`#list`).value = newVal; } else { document.querySelector(`#${key}`).value = newVal; } val = newVal; // Listen again for the new attribute observe(newVal); } }) } // Listen for all properties under the model observe(model); </script>
Debug the process on the console.

In the above practical operation, I first changed the value of the attribute name and triggered the setter. The page received a response and changed the model again The val attribute under the data object is also changed in response, which shows that we wanted to observe before. The problem that we couldn't monitor the change of object attribute has been solved under the above transformation.
Next, note that at last, I changed the value in the first subscript of the array attribute list to 5, and the page also got the listening result. However, after I changed the second subscript, I did not trigger the setter, and then deliberately changed the length of the list, or push did not trigger several groups of setters, and the page did not respond to the change.
Here are two questions:
a. I modified the value of the second subscript of the array list and called length and push. After changing the array list, the page did not respond to the change. What's the matter?
b. Back to the implementation in the Vue code at the beginning of the article, I changed the index attribute value of the list under Vue's data, and the page did not respond to the change, but here I changed the value in the list from 1 to 5, and the page responded. What's the matter? Please continue with questions a and b.
3. The detection property is an array object type
Here, we analyze the reason why a problem modifies the value of the array subscript and does not trigger the listener's setter function when calling the length and push methods to change the array. I've seen many articles about object before Defineproperty cannot listen for value changes in the array. Is that true?
Take a look at the following example. There is no page binding here, only object Whether the array elements monitored by defineproperty can monitor changes.

From the above code, first listen to all the attributes in the model array, and then modify the current array through various array methods. The following conclusions are drawn.
1. Directly modifying the existing elements in the array can be monitored.
2. If the operation method of array is to operate the monitored element that already exists, it can also trigger the setter to be monitored.
3. Only some special methods such as push, length and pop can not trigger setters, which is related to the internal implementation of the method and object The trigger implementation of the setter hook of defineproperty is related to the language level.
4. When changing the value of a subscript that exceeds the length of the array, the value change cannot be monitored. This is actually very easy to understand. Of course, non-existent attributes cannot be monitored, because the binding monitoring operation has been performed before, and the element attributes added later do not exist at the time of binding. Of course, there is no way to monitor it in advance.
So to sum up, object It is wrong to say that defineproperty cannot monitor the value change in the array. At the same time, it also obtains the answer to question a. the language level does not support the use of object Defineproperty listens for non-existent array elements, and cannot listen for array changes caused by some methods that can cause arrays.
4. Explore Vue source code and see how to realize array monitoring
For the b problem, you need to look at the source code of Vue, why object Defineproperty can obviously listen to the change of array value, but it is not implemented?
Here are some tips for me to look at the source code. If I directly open github to look at the source code line by line, it is very silly. Here, I directly use Vue cli to generate a Vue project locally, and then install it on the node_ You can try to view breakpoints in the Vue package under modules.
The test code is very simple, as follows;
import Vue from './node_modules/_vue@2.6.11@vue/dist/vue.runtime.common.dev' // Instantiate Vue and start it directly new Vue({ data () { return { list: [1, 3] } }, })

Explain the source code of this section. The following hasProto source code is to see whether there is a prototype. arrayMethods is an array method rewritten. The code flow is to directly modify the seven methods of push, pop, shift, unshift, splice, sort and reverse on the prototype if there is a prototype. If there is no prototype, Go to copyaugust to add these seven attributes and assign these seven methods without listening.
/** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { // Listening array elements observe(items[i]) } }
Finally, this The observearray function has a very simple internal implementation. It monitors the array elements. What does it mean? Changing the elements in the array cannot be monitored, but the value in the array is of object type. Modifying it can still get the monitoring response, such as changing list[0] Val can be monitored, but changing the list[0] cannot, but it still does not monitor the changes of the array itself.
Let's look at how arrayMethods overrides the array operation method.
// Record the API prototype method before the original Array is rewritten const arrayProto = Array.prototype // Copy the prototype above const arrayMethods = Object.create(arrayProto) // Method to override const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { def(arrayMethods, method, function mutator (...args) { // The original array method is called and executed const result = arrayProto[method].apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } // If the data is inserted, listen it up again if (inserted) ob.observeArray(inserted) // Trigger subscription, such as page update response, which is triggered here ob.dep.notify() return result }) })
From the above source code, you can see vue2 The idea of rewriting the array method in X. the rewritten array will manually trigger the effect of the response page every time after executing the original method of the array.
After reading the source code, the problem a comes to the bottom, vue2 X does not monitor the existing array elements, but monitors the method causing the array changes. When this method is triggered, it calls the mounted response page method to achieve the effect of page response.
However, please note that not all array methods have been rewritten, only push, pop, shift, unshift, splice, sort and reverse. As for why not use object Defineproperty to listen for changes to existing elements in the array.
The author you Yuxi's consideration is to bind each array element to monitor for performance reasons, which actually consumes a lot and benefits little.
issue address: https://github.com/vuejs/vue/issues/8562 .

Vue3. Data change monitoring for 0
The previous article said vue3 The monitoring of 0 adopts the new construction method Proxy of ES6 to Proxy the original object for change detection (students unfamiliar with Proxy can read the previous article). The existence of Proxy as a Proxy must go through the Proxy layer when asynchronously triggering data changes in the Model. In this layer, you can monitor the changes of arrays and various data types. See the following example.

It's perfect. Whether the array subscript assignment changes or the array method changes, it can be monitored. It can not only avoid the performance problems caused by monitoring each attribute of the array, but also solve the problem that the length method can't monitor the array changes when changing the array, such as pop and push methods.
Next, use Proxy and Reflect to implement vue3 Bidirectional binding under 0.
<!DOCTYPE html> <html> <div> name: <input id="name" /> val: <input id="val" /> list: <input id="list" /> </div> </html> <script> let model = { name: 'vue', data: { val: 1, }, list: [1] } function isObj (obj) { return typeof obj === 'object'; } // monitor function observe(data) { // Monitor all attributes Object.keys(data).map(key => { if (isObj(data[key])) { // Object type continues to listen for its properties data[key] = observe(data[key]); } }) return defineProxy(data); } // Generate Proxy proxy function defineProxy(obj) { return new Proxy(obj, { set(obj, key, val) { console.log(`attribute ${key}Change to ${val}`); compile(obj, key, val); return Reflect.set(...arguments); } }) } // Parser, responding to page changes function compile(obj, id, val) { if (Array.isArray(obj)) { // Array change document.querySelector('#list').value = model.list; } else { document.querySelector(`#${id}`).value = val; } } model= observe(model); </script>

After the implementation of proxy and Reflect, it is not necessary to consider whether the operation of the array triggers the setter. As long as the operation passes through the proxy layer, various operations will be captured to meet the requirements of page response.
summary
In vue2 The problem of array change monitoring in X is not object The definepropertype method can't monitor, but changes the monitoring method for the sake of comprehensive consideration of performance and benefit ratio. From the original idea of directly monitoring the result change to the method that monitoring will lead to the result change, that is, the rewriting of the array mentioned above.
Vue3 The Proxy method in 0 perfectly solves the problems in 2.0, so if you encounter the processing of array listening in Vue in future interviews, you must distinguish which version it is. The end of this article.
The above contents are summarized by yourself. It is inevitable that there will be errors or cognitive deviations. If you have any questions, I hope you can leave a message for correction to avoid mistaking people. If you have any questions, please leave a message and try your best to answer them.