Vue中模板編譯


  大家好,今天我給大家講解一下Vue中模板編譯是如何實現的。

 

  1. 首先我們先創建一個Vue的構造函數,在Vue中,如果有 el 的值我們就 new 一個 Compile 模板的實例,當然這個實例還沒有創建哈!

class Vue{
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        //這個根元素存在則編譯模板
        if(this.$el){
            //模板編譯
            new Compiler(this.$el,this);
        }
    }
}

 

 

 

   2. 這個模板編譯呢,主要是有這樣幾步

    1.  判斷 el 是不一個元素,如果是的話,我們直接用就好了,如果不是我們就再去獲取咯!
    2.  我們要把這個 el 元素中的所有內容全部放到文檔碎片中,這樣的話我們只需要編譯文檔碎片就好了,而不需要去在操作 dom,也就是實現了數據編譯
    3.  最后我們再把編譯好的文檔碎片放入這個 el 元素中

   接下來就讓我們創建一個 Compile 模板的構造函數吧!

class Compile{
      constructor(el,vm) {
        this.vm = vm;
        //判斷el屬性 是不是一個元素 不是就獲取
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        console.log(this.el);
        
        //把當前的節點元素 獲取到 放到內存中 創建文檔碎片
        let fragment = this.node2fragment(this.el);
        
        //模板編譯 用數據編譯
        this.compile(fragment);
        
        //把內容在塞到頁面中
        this.el.appendChild(fragment);
    }            
}

 

  

  上面的呢我們是不是用到了幾個方法,當然了,這個方法也還沒有寫呢。。。

  這幾個方法呢,都是Compile的原型上的! 我們來寫一下啦!

//獲取所有元素,放到內存中
    node2fragment(node){
        //創建一個文檔碎片
        let fragment = document.createDocumentFragment();
        let firstChild;
        
        //將node節點的的第一個節點給firstChild 如果node節點的的第一個節點為空則結束
        while(firstChild = node.firstChild){ 
            //appendChild具有移動性
            fragment.appendChild(firstChild);
        }
        return fragment;
    }
    
    // 是不是元素節點
    isElementNode(node){
        return node.nodeType === 1;
    }

 

 

   

  下面這個方法呢,就是模板的核心方法啦,用它來實現數據編譯

 

//用來編譯內存中的dom節點 核心方法
    compile(node){ 
        let childNodes = node.childNodes; //獲取node的所有子節點
        [...childNodes].forEach(child=>{
            if(this.isElementNode(child)){ //判斷是不是元素節點
            
                this.compileElement(child); //編譯元素指令
                
                //如果是元素節點的話 需要把自己傳不進去 再去遍歷子元素節點
                this.compile(child);
            }else{ //文本元素
                this.compileText(child); //編譯文本指令
            }
        })
    }

 

 

 

 

  這個幾個方法就是 compile 這個核心方法所用的方法!

  CompileUtil 是全局對象對象,分別儲存對應着不同的方法在下面將會創建

//判斷屬性是不是以 v- 開頭
    isDirective(attrName){
        return attrName.startsWith('v-');
        // return /^v-/.test(attrName)
    }
    
    //編譯元素的
    compileElement(node){
        let attributes = node.attributes; //類數組, 獲取所有node節點的屬性和屬性值
        
        attributes = [...attributes]
        //console.log(attributes)
        attributes.forEach(attr=>{ //是一個屬性對象attr
            let {name, value:expr} = attr; //:expr是給value起一個別名叫 expr **school.name
            //判斷是不是vue指令
            if(this.isDirective(name)){
                let [,directive] = name.split('-');
                let [directiveName, eventName] = directive.split(':');
                //需要調用不同的指令來處理 *** v-if v-modle v-show v-else
                CompileUtil[directiveName](node,expr,this.vm,eventName);
            }
        })
    }
    
    //編譯文本的
    compileText(node){ //判斷節點中是否包含 {{}}
        let content = node.textContent;
        if(/\{\{.+?\}\}/.test(content)){
            CompileUtil['text'](node,content,this.vm);
        }
    }

 

 

  因為 vue 中的指令不同,所以我們要調用不同的方法,這里呢,就創建了一個 CompileUtil 的全局對象對象,分別儲存對應着不同的方法

  

CompileUtil = {
    
    //根據表達式獲取對應的數據
    getVal(vm,expr){ 
        // reduce() 方法
         //  函數的參數 (第一參數)1.相加的初始值,2.循環出來的那一項,3.索引 4.循環的數組
         //  (第二個參數)初始值
         //  返回值:總和的結果
        return expr.split('.').reduce((data,current)=>{ //[school,name] 
            return data[current];
        },vm.$data);
    },
    model(node,expr,vm){ //node是節點  expr是表達式 vm是實例
        let fn = this.updater['modelUpdater'];
        let value = this.getVal(vm,expr);
        fn(node,value);
    },
    html(node,expr,vm,eventName){
        let fn = this.updater['htmlUpdater']
        
        let value = this.getVal(vm,expr);
        
        fn(node,value);
    },
    text(node,expr,vm){
        let fn = this.updater['textUpdater']
        //console.log(expr) :{{ school.name }}
        let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ 
            //console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"]
            
            return this.getVal(vm,args[1].trim());
        })
        // console.log(content) 
        fn(node,content);
    },
    updater: {
        //把數據插入到value中
        modelUpdater(node,value){ 
            node.value = value;
        },
        htmlUpdater(node,value){
            node.innerHTML = value;
        },
        //處理文本節點
        textUpdater(node,value){
            //textContent 屬性設置或返回指定節點的文本內容,以及它的所有后代。
            node.textContent = value;
        }
    }
}

 

 

這樣的話,我們的模板編譯就完成啦!復制代碼去試一下吧!

下面這個是一個模板編譯的整體代碼

/**
 * 模板編譯
 */
class Compiler{
    constructor(el,vm) {
        this.vm = vm;
        //判斷el屬性 是不是一個元素 不是就獲取
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        console.log(this.el);
        
        //把當前的節點元素 獲取到 放到內存中 創建文檔碎片
        let fragment = this.node2fragment(this.el);
        
        //把節點中的內容進行替換
        
        //模板編譯 用數據編譯
        this.compile(fragment);
        
        //把內容在塞到頁面中
        this.el.appendChild(fragment);
    }
    
    //判斷屬性是不是以 v- 開頭
    isDirective(attrName){
        return attrName.startsWith('v-');
        // return /^v-/.test(attrName)
    }
    
    //編譯元素的
    compileElement(node){
        let attributes = node.attributes; //類數組, 獲取所有node節點的屬性和屬性值
        
        attributes = [...attributes]
        //console.log(attributes)
        attributes.forEach(attr=>{ //是一個屬性對象attr
            let {name, value:expr} = attr; //:expr是給value起一個別名叫 expr **school.name
            //判斷是不是vue指令
            if(this.isDirective(name)){
                let [,directive] = name.split('-');
                let [directiveName, eventName] = directive.split(':');
                //需要調用不同的指令來處理 *** v-if v-modle v-show v-else
                CompileUtil[directiveName](node,expr,this.vm,eventName);
            }
        })
    }
    
    //編譯文本的
    compileText(node){ //判斷節點中是否包含 {{}}
        let content = node.textContent;
        if(/\{\{.+?\}\}/.test(content)){
            
            CompileUtil['text'](node,content,this.vm);
        }
        
    }
    
    //用來編譯內存中的dom節點 核心方法
    compile(node){ 
        let childNodes = node.childNodes; //獲取node的所有子節點
        [...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;
        
        //將node節點的的第一個節點給firstChild 如果node節點的的第一個節點為空則結束
        while(firstChild = node.firstChild){ 
            //appendChild具有移動性
            fragment.appendChild(firstChild);
        }
        return fragment;
    }
    
    // 是不是元素節點
    isElementNode(node){
        return node.nodeType === 1;
    }
}

CompileUtil = {
    
    //根據表達式獲取對應的數據
    getVal(vm,expr){ 
        // 7. reduce() 方法
         //  函數的參數 (第一參數)1.相加的初始值,2.循環出來的那一項,3.索引 4.循環的數組
         //  (第二個參數)初始值
         //  返回值:總和的結果
        return expr.split('.').reduce((data,current)=>{ //[school,name] 
            // console.log(data,current)
            return data[current];
        },vm.$data);
    },
    model(node,expr,vm){ //node是節點  expr是表達式 vm是實例
        console.log(node)
        let fn = this.updater['modelUpdater'];
        
        
        let value = this.getVal(vm,expr);
        // console.log(value)
        fn(node,value);
    },
    html(node,expr,vm){
        let fn = this.updater['htmlUpdater']
        
        let value = this.getVal(vm,expr);
        
        fn(node,value);
    },
    text(node,expr,vm){
        let fn = this.updater['textUpdater']
        //console.log(expr) :{{ school.name }}
        let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ 
            //console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"]
            
            return this.getVal(vm,args[1].trim());
        })
        // console.log(content) 
        fn(node,content);
    },
    updater: {
        //把數據插入到value中
        modelUpdater(node,value){ 
            node.value = value;
        },
        htmlUpdater(node,value){
            node.innerHTML = value;
        },
        //處理文本節點
        textUpdater(node,value){
            //textContent 屬性設置或返回指定節點的文本內容,以及它的所有后代。
            node.textContent = value;
        }
    }
}

class Vue{
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        //這個根元素存在則編譯模板
        if(this.$el){
            
            //模板編譯
            new Compiler(this.$el,this);
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM