Vue源码解析--实现一个指令解析器 Compile

前言

  compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图

大致思路

  因为遍历解析的过程有多次操作dom节点,为提高性能和效率,会先将vue实例根节点的el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中    

    递归遍历保证每个节点及子节点都会解析编译到 , 包括了{{}}表达式声明的文本节点 , 

    指令的声明规定是通过特定前缀的节点属性来标记

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="js/MVue.js"></script>
    <title>Document</title>
</head>
<body>
    <div id="example">
        <h3>
            {{person.name}}---{{person.age}}----{{person.fav}}
        </h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
        <h2>{{msg}}</h2>
        <div v-text="person.fav"></div>
        <div v-text="msg"></div>
        <div v-html="msg"></div>
        <input v-model='msg'>
        <button v-on:click='handle'>测试on</button>
        <button @click='handle'>测试@</button>
    </div>
    <script>
        //创建实例
        new MVue({
            el: "#example",
            data: {
                msg: 'hello worl',
                person: {
                    name: 'jack',
                    age: 18,
                    fav: "爱好"
                }
            },
            methods: {
                handle() {
                    console.log(this)
                }
            },
        })
    </script>
</body> 
</html>

MVue.js

const compileUtil = {
    getVal(expr,vm){
        return expr.split('.').reduce((data,currentVal) =>{
            return data[currentVal]
        },vm.$data)
    },
    text(node,expr,vm){
        let value;
        if (expr.indexOf('{{') !==-1) {
            value = expr.replace(/{{(.+?)}}/g, (...args)=>{             
                return this.getVal(args[1],vm);
             })
        }else{
            value =  this.getVal(expr,vm);
        }
        this.updater.textUpdater(node,value)
    },
    html(node,expr,vm){
       const value =  this.getVal(expr,vm);
       this.updater.htmlUpdater(node,value)    
    },
    model(node,expr,vm){
        const value =  this.getVal(expr,vm);
        this.updater.modelUpdater(node,value)   
    },
    on(node,expr,vm,eventName){
         let fn = vm.$options.methods && vm.$options.methods[expr];
        node.addEventListener(eventName,fn.bind(vm),false); 
    },
    bind(node,expr,vm,eventName){

    },
    updater:{
        textUpdater(node,value){
            node.textContent = value;
        },
        htmlUpdater(node,value){
            node.innerHTML = value;
        },
        modelUpdater(node,value){
            node.value = value;
        }
    }
}
class Complie{
    constructor(el,vm){
      this.el = this.isElementNode(el)? el:document.querySelector(el)
      this.vm = vm;
      //1.获取文档碎片对象 ,放入内存中编译,减少回流和重绘
      const fragment = this.node2Fragment(this.el);
    //   2.header编译模板
        this.compile(fragment)
    //3.追加字节点到根元素
      this.el.appendChild(fragment)
    }
    compile(fragment){
        //1.获取每一个子节点
        const childNodes = fragment.childNodes;
        [...childNodes].forEach(child =>{
            if (this.isElementNode(child)) {
                //是元素节点
                // console.log(child)
                //编译元素节点
                this.compileElement(child)
            } else {
                //是文本节点

                // 编译文本节点
                this.compileText(child)
            }
            if(child.childNodes && child.childNodes.length>0){
                this.compile(child)
            }
        })
    }
    compileElement(node){
        const attributes = node.attributes;
        [...attributes].forEach(attr=>{
            const {name,value} = attr;
            if (this.isDirective(name)) {//是一个指令 v-model v-text v-on:click 
               const [,direction] =  name.split('-');//text   on:click
               const[dirName,eventName] = direction.split(':') ;//分割on:click text  html on click
               //dirName== html || text || no 
               //compileUtil[dirName]需要在compileUtil中分别找到解析html text no 的方法
            //  传入 node value 需要知道哪个节点的哪个值 
            //传入vm 需要在拿到 data中value 对应的值
            //eventName 如果有事件 传入事件
            // console.log(node,value,this.vm,eventName)
                compileUtil[dirName](node,value,this.vm,eventName)  //策略模式  数据驱动视图
            //删除有指令的标签上的属性
                node.removeAttribute('v-'+ direction)
            } else if(this.isEventName(name)){//解  析@符操作
                let [,eventName] = name.split('@');    
                compileUtil['on'](node,value,this.vm,eventName)  //策略模式  数据驱动视图          
            }
        })       
    }
    compileText(node){
        const content = node.textContent;
        if (/{{(.+?)}}/.test(content)) {
            compileUtil['text'](node,content,this.vm)  //策略模式  数据驱动视图            
        }
    }
    node2Fragment(node){
        //创建文档碎片
        const f = document.createDocumentFragment();
        let firstChild;
        while(firstChild = node.firstChild){
            f.appendChild(firstChild)
        }
        return f;
    }
    isElementNode(el){
        return el.nodeType === 1
    }
    isDirective(attrName){ //判断是否已v-开头
        return attrName.startsWith('v-')
    }
    isEventName(attrName){
        return attrName.startsWith('@')       
    }
}

class MVue{
    constructor(options){
        this.$data = options.data;
        this.$el =options.el;
        this.$options = options;
        if (this.$el) {
            //指令解析器
            new Complie(this.$el,this)
        }
    }
}
原文地址:https://www.cnblogs.com/wxyblog/p/13471321.html