Vue中模板编译

  你们好,今天我给你们讲解一下Vue中模板编译是如何实现的。html

 

  1. 首先咱们先建立一个Vue的构造函数,在Vue中,若是有 el 的值咱们就 new 一个 Compile 模板的实例,固然这个实例尚未建立哈!vue

class Vue{
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        //这个根元素存在则编译模板
        if(this.$el){
            //模板编译
            new Compiler(this.$el,this);
        }
    }
}

 

 

 

   2. 这个模板编译呢,主要是有这样几步node

    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);
    }            
}

 

  

  上面的呢咱们是否是用到了几个方法,固然了,这个方法也尚未写呢。。。app

  这几个方法呢,都是Compile的原型上的! 咱们来写一下啦!dom

//获取全部元素,放到内存中
    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 这个核心方法所用的方法!this

  CompileUtil 是全局对象对象,分别储存对应着不一样的方法在下面将会建立spa

//判断属性是否是以 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 的全局对象对象,分别储存对应着不一样的方法code

  

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);
        }
    }
}
相关文章
相关标签/搜索