Source code of Vue listening data object changes, source code of vue listening object
When listening for changes in data objects, the easiest thing to think of is to create a table that needs to be monitored, regularly scan its values, and perform the corresponding operations if there are changes. However, this implementation method has a performance problem, if you want to monitor a large amount of data, it takes a long time to scan all objects at a time. Of course, some frameworks use this method, but they use very clever algorithms to improve performance, which is not in our scope.
The Data Objects in Vue are monitored by setting the new features of ES5 (ES7 is coming soon, And ES5 things are really not new) Object. set and get in defineProperty.
Target
This is similar to the first example in the official document, but it is simplified because it only introduces the monitoring of data objects and does not involve text parsing. Therefore, the text parsing-related content is discarded directly:
<div id="app"></div>var app = new Vue({ el: 'app', data: { message: 'Hello Vue!' }});
Browser display:
Hello Vue!
Enter the following information in the console:
App. message = 'changed! '
The content displayed in the browser will be modified.
Object. defineProperty
Reference the definition on MDN:
The Object. defineProperty () method defines a new property on an Object directly, or modifies an existing property and returns this Object.
Together with this, an Object. getOwnPropertyDescriptor ():
Object. getOwnPropertyDescriptor () returns the attribute descriptor corresponding to the previous property of the specified Object. (Self-owned attributes refer to attributes that are directly assigned to the object and do not need to be searched from the prototype chain)
The following example uses a simple and intuitive method to set setter and getter:
Var dep = []; function defineReactive (obj, key, val) {// if there is a custom property, use the custom property var property = Object. getOwnPropertyDescriptor (obj, key); if (property & property. retriable = false) {return;} var getter = property & property. get; var setter = property & property. set; Object. defineProperty (obj, key, {enumerable: true, retriable: true, get: function () {var value = getter? Getter. call (obj): val; dep. push (value); return value ;}, set: function (newVal) {var value = getter? Getter. call (obj): val; // if (newVal = value) {return;} if (setter) {setter. call (obj, newVal);} else {val = newVal;} console. log (dep );}});}
Var a = {}; defineReactive (a, 'A', 12); // call getter. 12 is pushed to dep. The value of dep is [12]. a; // call setter and output dep ([12]). a = 24; // call getter. 24 is pushed to dep. The value of dep is [12, 24]. a;
Observer
After Object. defineProperty is mentioned, it is necessary to start pulling the Observer. Observer, which is interpreted as "observer" in Chinese. What can we observe? Observe the changes in object property values. Therefore, the so-called observer is to add getter and setter to all attributes of the object. If the object's attributes still have attributes, such as {a: 'A '}}}, then, recursively add getter and setter to its attributes:
Function Observer (value) {this. value = value; this. walk (value);} Observer. prototype. walk = function (obj) {var keys = Object. keys (obj); for (var I = 0; I <keys. length; I ++) {// Add getter, setter defineReactive (obj, keys [I], obj [keys [I]) to all attributes;}; var dep = []; function defineReactive (obj, key, val) {// if there is a custom property, use the custom property var property = Object. getOwnPropertyDescriptor (obj, key); if (prope Rty & property. retriable = false) {return;} var getter = property & property. get; var setter = property & property. set; // recursively add getter, setter var childOb = observe (val), and Object to the attribute. defineProperty (obj, key, {enumerable: true, retriable: true, get: function () {var value = getter? Getter. call (obj): val; dep. push (value); return value ;}, set: function (newVal) {var value = getter? Getter. call (obj): val; // if (newVal = value) {return;} if (setter) {setter. call (obj, newVal);} else {val = newVal;} // Add getter, setter childOb = observe (newVal); console to the attribute of the new value. log (dep) ;}}) ;}function observe (value) {if (! Value | typeof value! = 'Object') {return;} return new Observer (value );}
Watcher
The Observer monitors data changes by setting the getter and setter of the data object. When data is acquired, set, or modified, it can be monitored and relevant actions can be taken.
Another question is, who asked you to listen?
The command is Watcher, which triggers operations only when Watcher obtains data. Similarly, when modifying data, it only performs Watcher-related operations.
So how do we Associate Observer and Watcher? Global variable! This global variable is modified only by Watcher. The Observer only reads and judges whether Watcher reads data based on the value of this global variable. This global variable can be appended to dep:
Dep.tar get = null;
According to the above, the Code is as follows:
Function Watcher (data, exp, cb) {this. data = data; this. exp = exp; this. cb = cb; this. value = this. get ();} Watcher. prototype. get = function () {// set the value for dep.tar get to inform Observer that this is the getter dep.tar get = this called by Watcher; // call getter to trigger the corresponding response var value = this. data [this. exp]; // dep.tar get restore dep.tar get = null; return value ;}; Watcher. prototype. update = function () {this. cb () ;}; function Observer (value) {th Is. value = value; this. walk (value);} Observer. prototype. walk = function (obj) {var keys = Object. keys (obj); for (var I = 0; I <keys. length; I ++) {// Add getter, setter defineReactive (obj, keys [I], obj [keys [I]) to all attributes;}; var dep = optional values includep.tar get = null; function defineReactive (obj, key, val) {// if there is a custom property, use the custom property var property = Object. getOwnPropertyDescriptor (obj, key); if (property & pro Perty. retriable = false) {return;} var getter = property & property. get; var setter = property & property. set; // recursively add getter, setter var childOb = observe (val), and Object to the attribute. defineProperty (obj, key, {enumerable: true, retriable: true, get: function () {var value = getter? Getter. call (obj): val; // If Watcher is listening, press the Watcher object into dep if(dep.tar get) {dep.push(dep.tar get);} return value ;}, set: function (newVal) {var value = getter? Getter. call (obj): val; // if (newVal = value) {return;} if (setter) {setter. call (obj, newVal);} else {val = newVal;} // Add getter, setter childOb = observe (newVal) to the attribute of the new value ); // execute the update method (var I = 0; I <dep. length; I ++) {dep [I]. update () ;}}) ;}function observe (value) {if (! Value | typeof value! = 'Object') {return;} return new Observer (value );}
var data = {a: 1};new Observer(data);new Watcher(data, 'a', function(){console.log('it works')});data.a =12;data.a =14;
The above basically implements data listening, and there must be a lot of bugs, but it is just a rough demo, just want to show a rough process, not very detailed.
Dep
In the preceding examples, dep is a global array. If a new Watcher is used, dep requires an additional Watcher instance. In this case, no matter which data is updated, the update of all Watcher instances will be executed, this is unacceptable.
Dep is abstracted and a constructor is created separately. It can be solved without being global:
function Dep() { this.subs = [];}Dep.prototype.addSub = function(sub) { this.subs.push(sub);};Dep.prototype.notify = function() { var subs = this.subs.slice(); for(var i = 0; i < subs.length; i++) { subs[i].update(); }}
You can use Dep to rewrite the above Code (of course, the Dep code here is not completely, it is just a rough idea ).
Vue instance proxy data object
The official document contains the following sentence:
Each Vue instance will proxy all the attributes in its data object.
Var data = {a: 1}; var vm = new Vue ({data: data}); vm. a === data. a //-> true // setting attributes will also affect the original data vm. a = 2data. a //-> 2 //... and vice versa. a = 3vm. a //-> 3
This kind of proxy looks very troublesome, but it can also be implemented through Object. defineProperty:
Function Vue (options) {var data = this. data = options. data; var keys = Object. keys (data); var I = keys. length; while (I --) {proxy (this, keys [I] ;}} function proxy (vm, key) {Object. defineProperty (vm, key, {retriable: true, enumerable: true, // directly obtain vm. data [key] value get: function () {return vm. data [key] ;}, // directly set vm when setting the value. data [key] value set: function (val) {vm. data [key] = val ;}};}
Create a Vue to achieve the initial goal
var Vue = (function() { var Watcher = function Watcher(vm, exp, cb) { this.vm = vm; this.exp = exp; this.cb = cb; this.value = this.get(); }; Watcher.prototype.get = function get() { Dep.target = this; var value = this.vm._data[this.exp]; Dep.target = null; return value; }; Watcher.prototype.addDep = function addDep(dep) { dep.addSub(this); }; Watcher.prototype.update = function update() { this.run(); }; Watcher.prototype.run = function run() { this.cb.call(this.vm); } var Dep = function Dep() { this.subs = []; }; Dep.prototype.addSub = function addSub(sub) { this.subs.push(sub); }; Dep.prototype.depend = function depend() { if(Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify() { var subs = this.subs.slice(); for(var i = 0; i < subs.length; i++) { subs[i].update(); } }; Dep.target = null; var Observer = function Observer(value) { this.value = value; this.dep = new Dep(); this.walk(value); }; Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for(var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]); } }; function defineReactive(obj, key, val) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if(property && property.configurable === false) { return; } var getter = property && property.get; var setter = property && property.set; var childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if(Dep.target) { dep.depend(); if(childOb) { childOb.dep.depend(); } } return value; }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; if(newVal === value) { return; } if(setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = observe(newVal); dep.notify(); } }); } function observe(value) { if(!value || typeof value !== 'object') { return; } return new Observer(value); } function Vue(options) { var vm = this; this._el = options.el; var data = this._data = options.data; var keys = Object.keys(data); var i = keys.length; while(i--) { proxy(this, keys[i]); } observe(data); var elem = document.getElementById(this._el); elem.innerHTML = vm.message; new Watcher(this, 'message', function() { elem.innerHTML = vm.message; }); } function proxy(vm, key) { Object.defineProperty(vm, key, { configurable: true, enumerable: true, get: function proxyGetter() { return vm._data[key]; }, set: function proxySetter(val) { vm._data[key] = val; } }); } return Vue;})();
<!DOCTYPE html>
References:
How to Implement observer and watcher in vue source code analysis
One of the early source code learning series of vue: How to monitor the changes of an object
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.