Angularjs has been in contact since a long time ago, and it has been learned that Angularjs is a dirty check for data monitoring and page update rendering. Then, after touching vue.js, I was once curious how vue.js monitored the data updates and re-rendered the page. Today, we will step by step to parse the principle of vue.js response, and to implement a simple demo.
First, let's start with some basic knowledge.
Basic Knowledge Object.defineproperty
ES5 adds the Object.defineproperty API, which allows us to set getter and setter for the object's properties so that we can hijack the user's value and assignment of the object's properties. such as the following code:
const obj = {};let val = 'cjg';Object.defineProperty(obj, 'name', { get() { console.log('劫持了你的取值操作啦'); return val; }, set(newVal) { console.log('劫持了你的赋值操作啦'); val = newVal; }});console.log(obj.name);obj.name = 'cwc';console.log(obj.name);
We hijacked the value and assignment of Obj[name by Object.defineproperty, so we can do a little bit here, for example, we can trigger the update page operation when Obj[name] is assigned.
Publish subscription Mode
The Publish subscription pattern is one of the more common design patterns, with two roles: Publisher and Subscriber. Multiple subscribers can subscribe to an event to the same publisher, and when the event occurs, the Publisher notifies all subscribers who subscribe to the event. Let's look at an example to understand the next.
class Dep { constructor() { this.subs = []; } // 增加订阅者 addSub(sub) { if (this.subs.indexOf(sub) < 0) { this.subs.push(sub); } } // 通知订阅者 notify() { this.subs.forEach((sub) => { sub.update(); }) }}const dep = new Dep();const sub = { update() { console.log('sub1 update') }}const sub1 = { update() { console.log('sub2 update'); }}dep.addSub(sub);dep.addSub(sub1);dep.notify(); // 通知订阅者事件发生,触发他们的更新函数
Hands-on practice
After we understand the Object.defineproperty and publish subscriber patterns, it is not difficult to think that Vue.js is based on the above two to achieve data monitoring.
- Vue.js first through the Object.defineproperty to listen to the data getter and setter hijacking, when the data properties are assigned/value, Vue.js can be aware of and do the corresponding processing.
- By subscribing to the publish pattern, we can create a publisher for each property of an object, and when another subscriber relies on this property, the Subscriber is added to the publisher's queue. With Object.defineproperty data hijacking, the publisher of the attribute notifies all subscribers of the update content when the property's setter is called.
Next, let's do it (see note for details):
Class Observer {constructor (data) {///If not an object, return if (!data | | typeof data!== ' object ') {return; } this.data = data; This.walk (); }//Data hijacking of incoming data walk () {for (let key in This.data) {this.definereactive (This.data, Key, This.data[key]); }}//Create a publication instance of the current property and use Object.defineproperty to hijack the current property. Definereactive (obj, key, Val) {//Create the publisher of the current property, const DEP = new DEP (); /* * Recursive the value of the child attribute for data hijacking, such as the following data * let data = {* Name: ' CJG ', * obj: {* Name: ' Zht ', * age:22 , * obj: {* Name: ' CJG ', * age:22, *} *}, *}; * We first Data hijack the outermost name and obj, then obj.name,obj.age the sub-properties of the Obj object, obj.obj the data hijacking, the layer recursively goes down until all the data has completed the data hijacking work. */New Observer (Val); Object.defineproperty (obj, key, {get () {//) if there is currently a dependency on the property, it is added to the Publisher's subscriber queue if (Dep.target) {D Ep.addsub (Dep.target); } return Val; }, set (newval) {if (val = = = newval) { Return } val = newval; New Observer (newval); Dep.notify (); })}}//the publisher, adding the watcher that relies on the property to the subs array, and when that property changes, invokes the update function of all watcher that depend on the property to trigger the update. Class Dep {constructor () {this.subs = []; } addSub (sub) {if (This.subs.indexOf (sub) < 0) {This.subs.push (sub); }} notify () {This.subs.forEach ((sub) = = {Sub.update (); })}}dep.target = null;//Observer class Watcher {/** *creates an instance of watcher. * @param {*} VM * @param {*} keys * @param {*} UPDATECB * @memberof Watcher */Constructor (VM, keys, UPDATECB) { THIS.VM = VM; This.keys = keys; THIS.UPDATECB = UPDATECB; This.value = null; This.get (); }//Based on VM and keys get the latest observations get () {dep.target = this; Const KEYS = This.keys.split ('. '); let value = THIS.VM; Keys.foreach (_key = {value = Value[_key]; }); This.value = value; Dep.target = null; return this.value; } update () {Const OLDVALUE = This.value; ConsT newvalue = This.get (); if (OldValue!== newvalue) {THIS.UPDATECB (OldValue, newvalue); }}}let data = {name: ' Cjg ', obj: {name: ' Zht ',},};new Observer (data),//Listen for the data object's Name property, and when Data.name discovers the change, the CB letter is triggered Number new Watcher (data, ' name ', (oldValue, newvalue) = {Console.log (oldValue, NewValue);}) Data.name = ' zht ';//listen for the Obj.name property of the data object, and when Data.obj.name discovers the change, it triggers the CB function new Watcher (data, ' Obj.name ', (OldValue, NewValue) = {Console.log (oldValue, NewValue);}) Data.obj.name = ' CWC ';d ata.obj.name = ' DMH ';
Conclusion
In this way, a simple, responsive data listener is complete. Of course, this is just a simple demo, to illustrate the principle of vue.js response, the real Vue.js source code will be more complex, because the addition of a lot of other logic.
Next I might associate it with HTML to implement V-model, computed, and {{}} syntax. Code address interested in Welcome to come together under the study. Click here to view the contents of the second section. If you feel that there is a harvest, please also point a praise, hey.