// Add getter s and setter s for all properties of data function defineReactive( obj,key,val,customSetter,shallow ) { // var dep = new Dep(); /*....Omit part...*/ var childOb = !shallow && observe(val); //Adding a backup dependency dep for an object 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(); //Dependent add watcher for set, array change, etc. if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify();//A change triggers the watcher to update } }); }
Vue.prototype.$watch = function ( expOrFn, cb, options ) { var vm = this; if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {}; options.user = true; //Add a watcher to the expOrFn that needs to be observed and execute cb if the value of expOrFn changes. //The expOrFn is parsed during the instantiation of the watcher and added to the def under the data data data involved in the expOrFn var watcher = new Watcher(vm, expOrFn, cb, options); //immediate==true executes watch handler immediately if (options.immediate) { cb.call(vm, watcher.value); } //Cancel observation function return function unwatchFn() { watcher.teardown(); } };
var Watcher = function Watcher( vm, expOrFn, cb, options, isRenderWatcher ) { this.vm = vm; if (isRenderWatcher) { vm._watcher = this; } vm._watchers.push(this); // options if (options) { this.deep = !!options.deep; //Whether to observe changes in the object's internal values this.user = !!options.user; this.lazy = !!options.lazy; this.sync = !!options.sync; } else { this.deep = this.user = this.lazy = this.sync = false; } this.cb = cb; // Functions executed when observing property changes this.id = ++uid$1; // uid for batching this.active = true; this.dirty = this.lazy; // for lazy watchers this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); this.expression = expOrFn.toString(); // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { // The data you will need to observe: string | Function | Object | Array, etc. parses as a.b.c and returns the function that accesses the expression this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = function () { }; "development" !== 'production' && warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } // this.get() will access data that needs to be observed this.value = this.lazy ? undefined : this.get(); }; /** * Evaluate the getter, and re-collect dependencies. */ Watcher.prototype.get = function get() { //this is the watcher instantiated in the $watch method pushTarget(this);speak this Assigned to Dep.target And cached before watcher var value; var vm = this.vm; try { //Access the data you need to observe and execute dep.depend() in the getter where you get the data; add the watcher instantiated in the $watch method to the dep under the corresponding data value = this.getter.call(vm, vm); } catch (e) { if (this.user) { handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\"")); } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value); } popTarget(); //Assign the previous watcher to Dep.target this.cleanupDeps(); } return value };
This method is executed when the observed object changes
Watcher.prototype.run = function run() { /*....Omitted section.... */ var value = this.get(); //retrieve info value var oldValue = this.value; //Save the old value this.value = value; this.cb.call(this.vm, value, oldValue); //Execute watch callback /*....Omitted section.... */ };
this.getter = parsePath(expOrFn); returns a function that accesses the property with parameters such as vm. $watch (info, function (new, old) {console.log (watch success)}; this.getter =function(obj){return obj.info}; and executes value = this.getter.call (vm, call) in the get method of watcher.Vm);, the get method that triggers the attribute, adds the watcher to the def object corresponding to the info attribute, executes traverse(value), accesses the attributes under the info (assuming info is object-only) object in turn, and accesses the info attribute recursively, so that the def object for all attributes under info and info will add the watcher factExample.
When we execute vm.info="change", the set method of info is issued, dep.notify() is executed; the watcher on which the departure info depends executes the run method of watcher, which implements listening
Example