1. 程式人生 > 其它 >Vue.js的MVVM的響應式原理-指令解析&初始化檢視

Vue.js的MVVM的響應式原理-指令解析&初始化檢視

Vue.js採用資料劫持結合釋出者-訂閱者模式的放方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在資料變動時釋出訊息給訂閱者,觸發響應的監聽回撥

Vue.js內部指令解析和檢視初始化

//MVue.js
const compileUtil = {
    getValue(expr, vm){
        //[person,name]
        return expr.split('.').reduce((data,currentVal)=>{
            return data[currentVal];
        },vm.$data)
    },
    text(node,expr,vm){ //expr:msg  學習MVVM實現原理 //三種情況 v-text="msg" v-text="person.name" {{}}
        // console.log(expr);
        let value;
        if(expr.indexOf("{{") > -1){
            //{{Person.name}}--{{Person.age}}
            value = expr.replace(/\{\{(.+?)\}\}/g, (...args)=>{
                return this.getValue(args[1], vm);
            })
        }else{
            value = this.getValue(expr, vm); 
        }
        this.updater.textupdater(node, value);
    },
    html(node,expr,vm){
        const value = this.getValue(expr, vm);
        this.updater.htmlUpdater(node, value);
    },
    model(node,expr,vm){
        const value = this.getValue(expr, vm);
        this.updater.modelUpdater(node, value);
    },
    on(node,expr,vm,eventName){
        let fn = vm.$options.methods[expr];
        node.addEventListener(eventName, fn.bind(vm), false);
    },
    bind(node, expr, vm, attrName){
        //
    },
    //更新的函式
    updater:{
        textupdater(node, value){
            node.textContent = value; 
        },
        htmlUpdater(node, value){
            node.innerHTML = value;
        },
        modelUpdater(node, value){
            node.value = value;
        }
    }
}

class Compile{
    constructor(el,vm){
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        //1.獲取文件碎片 放入記憶體中會減少頁面的迴流和重繪
        const fragment = this.node2Fragment(this.el);
        console.log(fragment)
        //2.編譯模板
        this.compile(fragment);
        //3.追加子元素到根元素
        this.el.appendChild(fragment);
    }
    compile(fragment){
        //1.獲取子節點
        const childNodes = fragment.childNodes;
        [...childNodes].forEach(child=>{
            // console.log(child)
            if(this.isElementNode(child)){
                //是元素節點
                //編譯元素節點
                // console.log("元素節點", child)
                this.compileElement(child);
            }else{
                //文字節點
                //編譯文字節點
                // console.log("文字節點", child)
                this.compileText(child);
            }
            
            //遞迴遍歷內部節點
            if(child.childNodes && child.childNodes.length){
                this.compile(child);
            }
        })
    }
    //編譯元素節點
    compileElement(node){
        const attributes = node.attributes;
        // console.log(attributes);
        [...attributes].forEach((attr)=>{
            const {name, value} = attr;
            // console.log(name);
            if(this.isDirective(name)){ //是一個指令 v-textv-html v-model v-on:chilk
                const [,dirctive] = name.split('-'); // text html model on:click
                const [dirName, eventName] = dirctive.split(':'); // text html model on
                // 更新資料 資料驅動檢視
                compileUtil[dirName](node, value, this.vm, eventName)
                // 刪除有指令的標籤的屬性
                node.removeAttribute('v-'+ dirctive);
            }else if(this.isEventName(name)){ // @click="handlerClick"
                let [,eventName] = name.split("@");
                compileUtil["on"](node, value, this.vm, eventName);

            }
        })
    }
    //判斷是否是@符號開頭
    isEventName(attrName){
        return attrName.startsWith("@");
    }
    //判斷是不是一個指令
    isDirective(attrName){
        return attrName.startsWith("v-");
    }
    //編譯文字節點
    compileText(node){
        // {{}} v-text
        const content = node.textContent;
        if(/\{\{(.+?)\}\}/.test(content)){
            compileUtil['text'](node, content, this.vm)
        }
    }
    node2Fragment(el){
        //建立文件碎片
        const f = document.createDocumentFragment();
        let firstChild;
        while(firstChild = el.firstChild){
            f.appendChild(firstChild);
        }
        return f;
    }
    //判斷是否是元素節點
    isElementNode(node){
        return node.nodeType === 1;
    }
}
class MVue{
    constructor(options){
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if(this.$el){
            //1.實現一個數據的觀察者
            //2.實現一個指令解析器Compile
            new Compile(this.$el, this);
        }
    }
}

持續更新ing...