First, MVVM application

MVVM is all about data changes, view updates, view changes, data changes.

Second, MVVM principle

I. Vue overall scheduling and Compiler template Compiler

Step 1: Create Vue instance directly new Vue, and HTML file is directly imported script tag, no longer use import, so to create a Vue class in JS, also do not need export to export the class! Vue class constructor options is the object passed from the created Vue instance with the $EL argument.

Step 2: If el exists in the object, you can find the HTML template (view) above, and replace the elements in the template that need to be replaced with data, also known as compile (compile template). The compiled template is called when there is an EL (pass in the $EL and vue instances so the template is found).

Step 3: Create a compiler template class that determines whether the object to compile is an element node before compiling. Check if el is an element node, if so use itself, otherwise get a bunch of element nodes corresponding to #app.

Step 4: HTML renders a web page and forms a DOM tree with two types of nodes: element nodes and text nodes. Element node: nodeType Returns 1; Attribute node: nodeType Returns 2.

Step 5: Get the template and move the contents of the template piece by piece into the memory space (document fragment), using appendChild to dynamically throw data into the document fragment.

Step 6: Replace the data in the document fragment (template compilation) and return the compiled data to the web page for rendering.



Get the node first and determine whether it is an element node or a text nodeSpecific operations for template compilation:

1) First get the attribute node, determine whether the attribute node is an instruction starting with V -, and find the node corresponding to the interpolation expression through the re;



2) Encapsulate an object (tool), which contains different processing methods corresponding to different instructions;

// Encapsulate method, Getval (vm, expr) {return expr.split(".").reduce((data, $data => {return data[current]}, vm.$data)}, Model (node, expr, vm) {let val = this.getVal (vm, expr) let fn = this.updater["modelUpdater"] fn(node, val)}, Let newContent = content.replace(/\{\{(.+?)) let newContent = content.replace(/\{\{(.+?)) \}\}/g, (... args) => { return this.getval(vm, args[1]) }) let fn = this.updater["textUpdater"] fn(node, newcontent) }, Updater: {// Update v-model instruction data modelUpdater(node, value) {node.value = value; }, // update the textContent data textUpdater(node, content) {node.textcontent = content; }}}Copy the code

3) Compile element node: encapsulate the processing method corresponding to v-Model, attach value to the input box, and replace the original dead data.

4) Compiling text nodes: encapsulate the corresponding processing methods of interpolation expressions and replace the contents in {{}} with re.

5) Real text node and element node compilation

Observer data hijacking

The Observer implements data hijacking by making data responsive (adding get() and set()) and sensing changes as they occur.

The method defineProperty can be used to refine a property when setting reactive data, adding get and set methods to it.

3. Watch the observer

When an element or text is compiled, methods are triggered to modify the data, and watcher() is created to enter observer mode. The old state is saved in the observer, the data changes to update the state, the callback function is called, and something is done based on the state.

Four, Dep

The Dep class stores observers; Push Watcher into the Dep when the data is fetched and notify Watcher to call the update method when the data changes.

Complete code

class Dep{ constructor(){ this.subs = []; } addSub(watcher){ this.subs.push(watcher) } notify(){ this.subs.forEach(watcher=>watcher.update()) } } class Watcher{ constructor(vm,expr,cb){ this.vm = vm; this.expr = expr; this.cb = cb; this.oldState = this.get(); } get(){ Dep.target = this let state = CompilerUtil.getval(this.vm,this.expr) Dep.target = null; return state; } update(){ let newstate = CompilerUtil.getval(this.vm,this.expr) if(newstate ! =this.oldState){ this.cb(newstate) } } } class Observer{ constructor(data){ this.observer(data) } observer(data){ if(data && typeof data=='object'){ for(let key in data){ this.defindReactive(data,key,data[key]) } } } defindReactive(obj,key,value){ this.observer(value) let dep = new Dep() Object.defineProperty(obj,key,{ get(){ Dep.target && dep.subs.push(dep.target) return value}, // set data set:(newval)=>{if(newval! = value){ this.observer(newval) value = newval; Dep.notify ()// Notify watcher to call update method}},})}} class Compiler{constructor(el,vm){this.el = this.iselementNode (el)? el : document.querySelector(el) this.vm = vm; let fragment = this.node2fragment(this.el) this.compile(fragment); this.el.appendChild(fragment) } compileElement(node){ let attributes = node.attributes; [...attributes].forEach(attr=>{ let {name,value:expr} = attr if(this.isDirective(name)){ let [,directive] = name.split("-") CompilerUtil[directive](node,expr,this.vm) } }) } isDirective(attrName){ return attrName.startsWith("v-") } compileText(node){ let content = node.textContent let reg = /\{\{(.+?) \} \} /; if(reg.test(content)){ CompilerUtil['text'](node,content,this.vm) } } compile(fragment){ let childNodes = fragment.childNodes; [...childNodes].forEach(child=>{ if(this.isElementNode(child)){ this.compileElement(child) this.compile(child) }else{ this.compileText(child) } }) } node2fragment(node){ let fragment = document.createDocumentFragment(); let firstChild; while(firstChild=node.firstChild){ fragment.appendChild(firstChild) } return fragment; } isElementNode(node){ return node.nodeType === 1; } } CompilerUtil = { getval(vm,expr){ return expr.split(".").reduce((data,current)=>{ return data[current] },vm.$data) }, getContentValue(vm,expr){ return expr.replace(/\{\{(.+?) \}\}/g,(... args)=>{ return this.getval(vm,args[1]) }) }, setValue(value,expr,vm){ expr.split(".").reduce((data,current,index,arr)=>{ if(index==arr.length-1){ data[current] = value } return data[current] },vm.$data) }, model(node,expr,vm){ let val = this.getval(vm,expr) let fn = this.updater["modelUpdater"] new Watcher(vm,expr,(newstate)=>{ fn(node,newstate) }) node.addEventListener('input',(e)=>{ let value = e.target.value this.setValue(value,expr,vm) }) fn(node,val) }, text(node,content,vm){ let newcontent = content.replace(/\{\{(.+?) \}\}/g,(... args)=>{ new Watcher(vm,args[1],()=>{ fn(node,this.getContentValue(vm,content)) }) return this.getval(vm,args[1]) }) let  fn = this.updater["textUpdater"] fn(node,newcontent) }, updater:{ modelUpdater(node,value){ node.value = value; }, textUpdater(node,content){ node.textContent = content; } } } class Vue{ constructor(options){ this.$el = options.el; this.$data = options.data; If (this.$el){new Observer(this.$data) // console.log(this.$data) // Need vm proxy this Compiler(this.$el,this)}} // Let vm proxy data proxyVm(data){for(let key in data){// console.log(key)//school // Console. log(data)//school object // console.log(data[key])// School object value // give the data key to the VM agent Object.defineproperty (this,key,{// vm.school) Get () get(){return data[key]}})}} // vue uses vue instance proxy data vm.$data.xxx-- >vm.xxxCopy the code

15, Observer implements data hijacking, turning data into responsive (add get() and set() to data), sensing data modification 16, and triggering methods to modify data when compiling elements and text. At this time, Watcher () is created and enters Observer mode 17. The old state is saved in the observer, the data changes to update the state, the callback function is called, and something is done based on the state. 18. The Dep class contains multiple Watchers. It’s where you put the Watcher when you get the data; Watcher is notified to call the update method when data changes

1. The Vue class is the highest level and is responsible for the overall scheduling. It calls constructor when new, and templates are compiled when EL is present. 2. The Compiler class is a compilation template. Converting element nodes into document fragments, compiling elements in memory, compiling text, and rendering compiled to web pages 3. Observer class is data hijacking; Make the data responsive (add get() and set() to the data), so that the data can be sensed when the data is changed. Watcher () 5 is called. The Dep class is used to store the observer. Push Watcher into the Dep when fetching data. When the data changes, watcher is notified to call update 6, which listens for an input event in the Input box

Compile templates: go to the page to find nodes with interpolation and v-instructions for data hijacking: make all data responsive. Get data, trigger get method creates watcher method; Set calls notify notifies all the Watchers in the Dep to call its update method, which calls a callback to reassign the data