• 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);
            }
        }
    }
  • 相关阅读:
    Mayan游戏 (codevs 1136)题解
    虫食算 (codevs 1064)题解
    靶形数独 (codevs 1174)题解
    黑白棋游戏 (codevs 2743)题解
    神经网络 (codevs 1088) 题解
    The Rotation Game (POJ 2286) 题解
    倒水问题 (codevs 1226) 题解
    银河英雄传说 (codevs 1540) 题解
    生日蛋糕 (codevs 1710) 题解
    第一章 1.11 高阶函数
  • 原文地址:https://www.cnblogs.com/nie5135257/p/12110027.html
Copyright © 2020-2023  润新知