Vue's responsiveness
Anyone who has used the Vue framework should know that data driver is the core of the Vue framework, and data bidirectional binding is one of its major features. According to the official explanation, we can know the simple principle of responsiveness more clearly.
Responsive principle of Vue2
When you pass a normal JavaScript object into a Vue instance as a data option, Vue will iterate over all properties of this object and use to convert all of these properties into getter/setter. It is a feature in ES5 that cannot shim, which is why Vue does not support IE8 and lower versions of browsers.
These getters/setters are invisible to the user, but internally they allow Vue to track dependencies and notify changes when the property is accessed and modified. It should be noted here that different browsers format getter/setter differently when printing data objects on the console, so it is recommended to install vue-devtools to get a more user-friendly interface for checking data.
Each component instance corresponds to a watcher instance, which records the "contacted" data property as a dependency during component rendering. Then, when the setter of the dependency is fired, the watcher is notified, causing its associated components to re-render.
Responsive principle of Vue3
Implementation principle:
Through Proxy: Intercept changes in any attribute in the object, including: read and write attribute values, increase attributes, delete attributes, etc.
By Reffect: Operate on the properties of the source object
new Proxy(data,{ //Intercept the read property value get(target, prop){ return (target, prop) }, //Intercept set attribute value or add new attribute set(target, prop, value){ return (target, prop, value) }, //Intercept and delete attributes deleteProperty(target, prop){ return (target, prop) } })
The responsive principles of Vue2 and Vue3 are actually similar, but Vue3's proxy encapsulation and independence are relatively stronger and more flexible. However, what we see is the simplest and most basic responsive principle. If we want to understand more deeply how Vue uses this principle to implement various bidirectional binding and data rendering operations in the framework, we can analyze its source code.
Understand responsiveness
1. Data Initialization
new Vue({ el: "#app", router, store, render: (h) => h(App), });
This code must be very familiar to everyone. This is the process of Vue instantiation. From the new operator, we can see that Vue is actually a constructor, nothing special. The passed parameter is an object, which we call options.
// src/ import { initMixin } from "./"; // Vue is a constructor instantiated through new keywordsfunction Vue(options) { // Vue initialization work begins here this._init(options); } // _init method is a method of mounting the Vue prototype by introducing files to mount the prototype.// This practice is beneficial for code segmentationinitMixin(Vue); export default Vue;
Because Vue initialization may handle many things, such as data processing, event processing, life cycle processing, etc., dividing different files is conducive to code segmentation.
// src/ import { initState } from "./state"; export function initMixin(Vue) { ._init = function (options) { const vm = this; // This here represents the object (init object) that calls the _init method // this.$options is the attribute passed in when the user new Vue is vm.$options = options; // Initialization status initState(vm); }; }
initMixin mounts the _init method on the Vue prototype for Vue instance calls.
// src/ import { observe } from "./observer/"; export function initState(vm) { // Get the incoming data object const opts = vm.$options; if () { initProps(vm); } if () { initMethod(vm); } if () { // Initialize data initData(vm); } if () { initComputed(vm); } if () { initWatch(vm); } } // Initialize datafunction initData(vm) { let data = vm.$; // The _data attribute of the instance is the incoming data // Vue component data recommends using functions to prevent data from being shared between components data = vm._data = typeof data === "function" ? (vm) : data || {}; // Proxy data to vm, that is, Vue instance, we can use this._data.a for (let key in data) { proxy(vm, `_data`, key); } // Observe the data --Responsive Data Core observe(data); } // Data Agentfunction proxy(object, sourceKey, key) { (object, key, { get() { return object[sourceKey][key]; }, set(newValue) { object[sourceKey][key] = newValue; }, }); }
① Through this code, you can get information that is very helpful to us when developing Vue projects, that is, the order of data initialization is prop>methods>data>computed>watch. Regarding whether we can call the prop value in data, if we know the order of data rendering, it will be solved.
② In addition, through this source code, we can also obtain information. Data is encapsulated with function function, rather than object Object, in order to avoid data sharing among components, so that each component can have an independent variable scope.
2. Object data hijacking
The hijacking of object data is actually easy to understand. The code adds a corresponding listener to each parameter in the object through recursion, so when the object data changes, the listener will naturally be triggered.
Here we can get an information that the object only has a monitor mark during the initialization stage. When we add new parameters to the object, we must use the built-in functions s e t and set and set and delete provided by Vue to dynamically operate the object parameters. Otherwise, we will directly add new parameters, which will not have the effect of two-way binding at this time.
// src/obserber/ class Observer { // Observation values constructor(value) { (value); } walk(data) { // All properties on the object are observed in sequence let keys = (data); for (let i = 0; i < ; i++) { let key = keys[i]; let value = data[key]; defineReactive(data, key, value); } } } // Data hijacking core compatibility is ie9 and abovefunction defineReactive(data, key, value) { observe(value); // Recursive key // --If the value is still an object, it will continue to go through odefineReactive. It will traverse layer by layer until the value is not an object. // think? If the Vue data nesting level is too deep >> Performance will be affected (data, key, { get() { ("Get Value"); return value; }, set(newValue) { if (newValue === value) return; ("Set Value"); value = newValue; }, }); } export function observe(value) { // If the object or array is passed, perform attribute hijacking if ( (value) === "[object Object]" || (value) ) { return new Observer(value); } }
Listening of arrays
// src/obserber/ import { arrayMethods } from "./array"; class Observer { constructor(value) { if ((value)) { // Additional judgments are made on the array here // Intercept seven methods of array by rewriting the array prototype method value.__proto__ = arrayMethods; // If the array also contains an array, recursive judgment is required (value); } else { (value); } } observeArray(items) { for (let i = 0; i < ; i++) { observe(items[i]); } } }
Listening of an array is to judge each element of the array. If the array also contains an array, it needs to be monitored recursively. If it is not an array element, it is directly monitored and set.
Because intercepting array subscripts is too wasteful, the array judgment is added to the data parameters passed in the Observer constructor.
// src/obserber/ class Observer { // Observation values constructor(value) { (value, "__ob__", { // The value refers to an instance of Observer value: this, // Cannot be enumerated enumerable: false, writable: true, configurable: true, }); } }
Finally, in order to facilitate our operation of arrays, Vue rewritten some common methods of arrays. When we call these methods, the Vue underlying layer will automatically add corresponding listeners for us, so we don’t need to render and bind the elements again.
// src/obserber/ // Keep the array prototype firstconst arrayProto = ; // Then inherit arrayMethods from the array prototype// Here is the idea of slice programming (AOP)--the dynamic extension function without breaking the encapsulationexport const arrayMethods = (arrayProto); let methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "reverse", "sort", ]; ((method) => { arrayMethods[method] = function (...args) { // Here the execution results of the prototype method are preserved const result = arrayProto[method].apply(this, args); // This sentence is the key // This represents the data itself. For example, the data is {a:[1,2,3]}. Then we use (4) this is a ob, which is a.__ob__ This attribute is added to the previous code. It represents that the data has been responsively observed and pointed to the Observer instance. const ob = this.__ob__; // The flag here means that there are new operations in the array let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = (2); default: break; } // If there is a new element inserted is an array, call the observeArray of the Observer instance to observe each item of the array. if (inserted) (inserted); // We can also detect that the array has changed here and trigger the view update operation-the subsequent source code will be revealed return result; }; });
This is the end of this article about a deep understanding of the principle of Vue responsiveness and its implementation. For more related content on Vue responsiveness, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!