zoukankan      html  css  js  c++  java
  • 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);
            }
        }
    }
  • 相关阅读:
    160422、Highcharts后台获取数据
    160421、MyBatis批量插入数据
    微信小程序wx:for循环
    【小程序】获取到的e.target与e.currentTarget区别
    微信小程序如何像vue一样在动态绑定类名
    发布 Vant
    ZanUI-WeApp -- 一个颜值高、好用、易扩展的微信小程序 UI 库
    微信小程序 使用wxParse解析html
    精通移动端布局
    axios interceptors 拦截 , 页面跳转, token 验证 Vue+axios实现登陆拦截,axios封装(报错,鉴权,跳转,拦截,提示)
  • 原文地址:https://www.cnblogs.com/nie5135257/p/12110027.html
Copyright © 2011-2022 走看看