Detailed description of Vue event driver and dependency tracking, detailed description of vue event tracking
A previous analysis on Vue data binding principles needs to be reviewed recently and sent to the essay by the way.
In the previous implementation of an Mvvmsetter To observemodel.viewModel Bindmodel . WhenmodelChange, update allviewModelTo render the new value to the interface. At the same time, throughv-model All boundinputAnd passaddEventListenerEvent will be updatedmodel To complete two-way binding.
But that program is not only used to understanddefinePropertyAnd others are worthless.
- No compilation node.
- No processing expression dependency.
Here I will solve the problem of expression dependency. I will introduce the vue template compilation in the next section.
Define getter & setter for Data
class Observer { constructor(data) { this._data = data; this.walk(this._data); } walk(data) { Object.keys(data).forEach((key) => { this.defineRective(data, key, data[key]) }) }; defineRective(vm, key, value) { var self = this; if (value && typeof value === "object") { this.walk(value); } Object.defineProperty(vm, key, { get: function() { return value; }, set: function(newVal) { if (value != newVal) { if (newVal && typeof newVal === "object") { self.walk(newVal); } value = newVal; } } }) }}module.exports = Observer;
In this way, each attribute is addedgetter Andsetter When the attribute is an object, it is recursively added.
Once the attribute value is obtained or assigned a value to the attributeget Orset Whenset, That ismodelYou can publish a message to notify allviewModel Update.
DefineRective (vm, key, value) {// stores the dependency expression of this attribute in the closure. Var dep = new Dep (); var self = this; if (value & typeof value = "object") {this. walk (value);} Object. defineProperty (vm, key, {get: function () {return value;}, set: function (newVal) {if (value! = NewVal) {if (newVal & typeof newVal = "object") {self. walk (newVal);} value = newVal; // notify all viewmodels to update dep. notify ();}}})}
How to defineDep What about ??
Class Dep {constructor () {// dependency list this. dependences = [];} // Add dependency addDep (watcher) {if (watcher) {this. dependences. push (watcher) ;}}// notify all dependencies to update notify () {this. dependences. forEach (watcher) => {watcher. update () ;}} module. exports = Dep;
Each dependency here isWatcher .
See how to defineWatcher
EachWatcher There will be a uniqueidIt has an expression and a callback function.
For example, expression a +b; Will be accessed during get computinga Andb Because JavaScript is a single thread, only one JavaScript code is executed at any time.Dep.target As a global variable to indicate the currentWatcher And thencompute Accessa ,b , Triggera Andb OfgetterIn getter Dep.target Add as dependency.
Oncea Andb Ofset Trigger, callupdate Function to update the dependent value.
Var uid = 0; class Watcher {constructor (viewModel, exp, callback) {this. viewModel = viewModel; this. id = uid ++; this. exp = exp; this. callback = callback; this. oldValue = ""; this. update ();} get () {Dep.tar get = this; var res = this. compute (this. viewModel, this. exp); Dep.tar get = null; return res;} update () {var newValue = this. get (); if (this. oldValue = newValue) {return;} // update Dom in callback this. callback (newValue, this. oldValue); this. oldValue = newValue;} compute (viewModel, exp) {var res = replaceWith (viewModel, exp); return res ;}} module. exports = Watcher;
Because the current expression needs to be executed under the current model, replaceWith function is used to replace.
Add dependency through get
Object.defineProperty(vm, key, { get: function() { var watcher = Dep.target; if (watcher && !dep.dependences[watcher.id]) { dep.addDep(watcher); } return value; }, set: function(newVal) { if (value != newVal) { if (newVal && typeof newVal === "object") { self.walk(newVal); } value = newVal; dep.notify(); } }})
This method of adding dependencies is too clever.
Here I drew a picture to describe it.
Finally, let's test the code.
const Observer = require('./Observer.js');const Watcher = require('./watcher.js');var data = { a: 10, b: { c: 5, d: { e: 20, } }}var observe = new Observer(data);var watcher = new Watcher(data, "a+b.c", function(newValue, oldValue) { console.log("new value is " + newValue); console.log("oldValue is " + oldValue);});console.log("\r\n");console.log("a has changed to 50,then the expr should has value 55");data.a = 50;console.log("\r\n");console.log("b.c has changed to 50,then the expr should has value 122");data.b.c = 72;;console.log("\r\n");console.log("b.c has reseted an object,then the expr should has value 80");data.b = { c: 30 }
OK
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.