zoukankan      html  css  js  c++  java
  • 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)
            }
        }
    }
  • 相关阅读:
    在centos7上使用packstack安装openstack
    解决 React-Native: Android project not found. Maybe run react-native android first?
    javascript_11-函数面试题
    javascript_10-函数
    前端面试记录NO.1
    javascript_09-数组
    javascript_08-while 和 do while
    javascript_07-break 和 continue
    javascript_05-操作符
    javascript_06-控制流程
  • 原文地址:https://www.cnblogs.com/wxyblog/p/13471321.html
Copyright © 2011-2022 走看看