Vue hijacks the setter and getter of each attribute through Object.defineProperty () in a way of data hijacking and publishing subscriber pattern. When data changes, vUE publishes messages to the dependent collector Dep(subscriber) to inform the observer to make the corresponding callback function to update the view. Language arrangement: MVVM, as a binding entry, integrates observer, Compile, and Watcher, listens to model data changes through observer, compile parse template instructions, and finally uses Watcher to build a world name bridge between Observer and Compile. Reach data changes -> View update; View Interactive Changes (INPUT) -> Bidirectional binding effect of data model changes.

First, MVue instance

// Lev vm = new MVue() Let vm = new MVue({el:'#app', data:{MSG :' learn MVVM implementation principle ', person:{name:' ma ', age:18, fav:' girl ', person:{name:' ma ', age:18, fav:' girl ', Obj :{a:'123'}}, htmlStr:' Learn, }, methods:{handlerClick(){// const p = new Proxy(target, Handler) hijacked // Proxy objects used to define custom behavior for basic operations (such as property lookups, assignments, enumerations, function calls, and so on). // target // The target object to be wrapped with Proxy (this can be any type of object, including a native array, a function, or even another Proxy). // handler // An object that usually has functions as attributes. The functions in each attribute define the behavior of agent P when performing various operations. // console.log(p) console.log(this) this. MSG = '11111' this.$data.person.name = ' '}}})Copy the code

Mvue and proxyData agent

Class MVue{constructor(options) {// This points to MVue this.$el = options.el this.$data = options.data this options if(this.$el){ //1. New Observer(this.$data) //2 New Compile(this.$el,this) this.proxyData(this.$data) // hijole this.$data to this}} proxyData(data){for(const) Key in data){object.defineProperty (this,key,{//this=MVue get(){return data[key] // data:{MSG :' learn MVVM '}}, set(newVal){ data[key] = newVal } }) } } }Copy the code

ProxyData agent before

After proxyData agent

Compile parser

class Compile{ constructor(el,vm) { console.log('compile') this.el = this.isElementNode(el) ? el : document.querySelector(el) // console.log(this.el,'el') this.vm = vm // 1. Const fragment = this.node2fragment (this.el) // console.log(fragment,'fragment') //2 Compile template this.compile(fragment) //3. AppendChild elements to the root element this.el.appendchild (fragment)} compile(fragment){// 1. Const childNodes = fragment.childnodes childNodes. ForEach (child=>{// console.log(child,'child')) If (this.iselementNode (child)){// is the element node // builds the element node // console.log(' element node ',child) this.pileElement (child)}else{// Text node Log (' text node ',child) this.piletext (child)} if(child.childnodes && child.childnodes.length){// compile text node // console.log(' text node ',child) this.piletext (child)} if(child.childnodes && child.childnodes.length){// This.compile (child)}})} compileElement(node){const attributes = node.attributes // console.log(Object.keys(attributes),'attributes') //{0: v-text, v-text: v-text, length: 1} <div v-text="msg"></div> Object.keys(attributes).forEach(item=>{ let attr = attributes[item] //attributes Property returns a collection of properties for the specified node, NamedNodeMap. You can use the Length // attribute to determine the number of attributes, and then you can walk through all the attribute nodes and extract the information you need. // v-text:name = msg:value v-on:click="handlerClick" //@click="handlerclick" v-bind:src = "img" const {name,value} = Attr // structure assignment gets name, Value // console.log(attr) if(this.isdirective (name)){v-text,v-html,v-model, V-on :click const [,directive] = name.split('-') // text,html,model,on:click const [dirName,eventName] = directive.split(':') / / dirName: text, HTML. The model, data driven view on / / update data compileUtil [dirName] (node, the value, enclosing the vm, eventName) / / delete the instructions on the label attribute node.removeAttribute('v-'+directive) }else if(this.isEventName(name)){ //@click="handlerclick" let [,eventName] = name.split('@') compileUtil['on'](node,value,this.vm,eventName) } }) } isEventName(attrName){ return attrName.startsWith('@') } compileText(node){ //{{}} v-text // console.log(node.textContent) const content =node.textContent if(/\{\{(.+?) \}\}/.test(content)){ // console.log(content) compileUtil['text'](node,content,this.vm) } } isDirective(attrName){ // The startsWith() method checks if the string startsWith the specified substring. // Returns true if it begins with the specified substring, false otherwise. // The startsWith() method is case-sensitive. Return attrName. StartsWith (' v - ')} node2Fragment (el) {/ / create a document fragment const f = document. CreateDocumentFragment () //createDocumentFragment is used to create a virtual node object, or to create a DocumentFragment node. It can contain various types of nodes. The inherited parentNode property is always null. When a DocumentFragment node is requested to be inserted into the document tree, it is not the DocumentFragment itself that is being inserted, but all of its descendants, i.e. the nodes in parentheses. This feature makes the DocumentFragment a placeholder for nodes that are inserted into the document at once. It is also useful for cutting, copying, and pasting documents. // If a node in the original DOM tree is added to the DocumentFragment using the appendChid method, the original node is removed. let firstChild; While (firstChild = el.firstChild){// If the condition is true, F.appendchild (firstChild) //appendChild } return f} isElementNode(node){return node.nodeType === 1}}Copy the code

More on createDocumentFragment usage

CompileUtil

CompileUtil ={getVal(expr,vm){// fetch //[person, name] return expr.split('.').reduce((data,currentVal)=>{//[person,fav] // console.log(data) // console.log(currentVal) return data[currentVal] // Here the get method of Object.defineProType () is triggered And push watcher into subs},vm.$data)}, Return expr.split('.').reduce((data,currentVal)=>{//[person,fav] If (typeof data[currentVal] == 'object') return data[currentVal]} data[currentVal] = inputVal},vm.$data) }, getContent(expr,vm){ return expr.replace(/\{\{(.+?)\}\}/g,(... Arguments)=>{return this.getVal(arguments[1],vm)})}, text(node,expr,vm){//node: node,expr: MSG = "data. MSG,vm: whole instance v-text:'person.fav' {{}} let value; if(expr.indexOf('{{')! =-1){ // {{person.name}} -- {{person.age}} value = expr.replace(/\{\{(.+?)\}\}/g,(... Arguments)=>{// console.log(arguments[1]) New Watcher(VM,arguments[1],()=>{// Compile // Console. log(this.getContent(expr,vm)) this.updater.textUpdater(node,this.getContent(expr,vm))) // Triggers the HTML method callback to return the new value}) // When a function is called (whether called or called), a hidden object named Arguments is automatically generated inside the function, which is an array of classes. // console.log(arguments) return this.getVal(arguments[1],vm) }) }else{ value = this.getVal(expr,vm); } // const value = vm.$data[expr] this.updater.textUpdater(node,value); }, HTML (node,expr,vm){//computil HTML method const value = this.getVal(expr,vm) new Watcher(vm,expr,(newVal)=>{// Watcher update view HtmlUpdater (node,newVal) // trigger the HTML method callback back to the new value}) // Watcher and data binding to listen for data HtmlUpdater (node,value)}, model(node,expr,vm){const value = this.getVal(expr,vm) // Data = "view new Watcher(vm,expr,(newVal)=>{// Watcher update view compile Enclosing updater. ModelUpdater (node, newVal) / / triggers the HTML method callback back new value}) / / view = "data =" view node. The addEventListener (' input, (e) = > {/ / set the value this.setVal(expr,vm,e.target.value) }) this.updater.modelUpdater(node,value) }, on(node,expr,vm,eventName){ let fn = vm.$options.methods && vm.$options.methods[expr] Node.addeventlistener (eventName,fn.bind(vm),false) //fn.bind(vm) points to the VM instance: MVue //fn.bind(this) points to class compileUtil //fn points to caller, button }, updater:{ modelUpdater(node,value){ node.value = value }, htmlUpdater(node,value){ node.innerHTML = value }, TextUpdater (node,value){//textContent sets or returns the textContent of the specified node. // If you set the textContent property, any child nodes will be removed and replaced by the text node of the specified string. node.textContent = value } } }Copy the code

Third, the Observer

class Observer{ constructor(data){ console.log('observer') this.observer(data) } observer(data){ /* { person:{ name:'', Fav :{a: "}}} */ if(data && typeof data=='object'){// Determine whether to iterate and hijack // console.log(object.keys (data)) Object.keys(data).foreach (key=>{// console.log(key,'key') this.definereactive (data,key,data[key])})}} // Hijack and listen for all attributes DefineReactive (obj,key,value){this.observer(value) const dep = new dep () // console.log('push') // console.log(dep.target) initializing data is not compiled. DefineProperty (obj,key,{// enumerable:true, // enumerate different :false, runs without any additional information. // Change get(){// Add observers to dep when subscription data changes and when DEP has watcher // Create watcher at compile time push subs dep.target && dep.addSub(dep.target) // Console. log(dep. target,'target2') return value}, set (newVal)=>{// person = {age:1} assign to age: get set this.observer(newVal) if(newVal! ==value){value = newVal} // tells deP to notify the change dep.notify()}, // get // property getter function, if there is no getter, undefined. This function is called when the property is accessed. // No arguments are passed, but this is not necessarily the object that defines the attribute due to inheritance. The return value of this function is used as the value of the property. Setter function for // set // property, undefined if there is no setter. This function is called when the property value is modified. // This method takes an argument (the new value being assigned) and passes in the this object at the time of the assignment. }}})Copy the code

Dep depends on the collector

Constructor () {this.subs = [] console.log(' Dep ')} // Collect observer watcher addSub(watcher){// Console.log (watcher) this.subs.push(watcher)} // Notify observer update notify(){// console.log(' notify observer ',this.subs) this.subs.forEach(w=>w.update()) } }Copy the code

The Watcher

Class Watcher{// Set up Watcher when initializing data, bind to dep.target, Constructor (vm,expr,cb) {console.log('watcher') this.vm = vm this.expr = expr this.cb = cb // Save the old values this.oldVal = this.getOldVal() } getOldVal(){ Dep.target = this let oldVal = compileUtil.getVal(this.expr,this.vm) // Push watcher into subs via compileutil. getVal get method object.defineProType ().  update(){ let newVal = compileUtil.getVal(this.expr,this.vm) if(newVal! == this.oldval){this.cb(newVal) //new watcher created cb}}}Copy the code

Code implementation link

Video viewing please search vue source code design in Bilibili