MVVM框架-vue基础原理实现

/* 定义一个类,用来创建Vue实例  */
class Vue{
    constructor(options = {}){
        // 给Vue实例添加属性
        this.$el = options.el;
        this.$data = options.data;
        this.$methods = options.methods;

        // 如果指定el,对el进行解析
        if(this.$el){
            //compile负责编译解析模板内容
            // 需要:数据和模板
            new Compile(this.$el, this)
        }
    }
}

定义vue实例,添加属性,对指定模板进行解析;

/* Compile专门负责解析模板内容 */
class Compile {
    // 参数一:模板
    // 参数二:Vue实例
    constructor(el, vm){
        // el:new vue传递的选择器
        this.el = typeof(el) === "string" ? document.querySelector(el) : el;
        // vm: new vue传递的实例
        this.vm = vm;       
        //编译模板
        if(this.el){
            //1.把el中所有子节点都放入内存中, fragment
            let fragment = this.node2fragment(this.el);
            //2.在内存中编译fragment
            this.compile(fragment);
            //3.把fragment一次性加到页面中
            this.el.appendChild(fragment);
        }
    }
    /* 核心方法 */
    node2fragment(node){
        let fragment = document.createDocumentFragment();
        //获取所有的子节点
        let childNodes = node.childNodes;
        this.toArray(childNodes).forEach(node => {
            //把el中所有子节点添加到文档碎片fragment中
            fragment.appendChild(node)
        })
        return fragment;
    }
    /**
     * 编译文档碎片(内存中)
     * @param {*} fragment 
     */
    compile(fragment){        
        let childNodes = fragment.childNodes;
        //编译子节点
        this.toArray(childNodes).forEach( node => {                       
            if(this.isElementNode(node)){
                //如果是元素,需要解析指令
                this.compileElement(node);
            }
            if(this.isTextNode(node)){
                //如果是文本节点,需要解析差值表达式
                this.compileText(node);
            }
            // 如果当前节点还有子节点,需要继续解析
            if(node.childNodes && node.childNodes.length > 0 ){
                this.compile(node)
            }
        })
    }
    // 解析html标签
    compileElement(node){
        //console.log(node)
        // 1.获取当前节点下所有的属性
        let attributes = node.attributes;
        this.toArray(attributes).forEach(attr => {
            // 2.解析vue的指令(所有以v-开头的属性)
            let attrName = attr.name;
            if(this.isDirective(attrName)){
                let type = attrName.slice(2);
                let expr = attr.value;
                // 如果是v-on指令
                if(this.isEventDirective(type)){
                    CompileUtil['eventHandler'](node, this.vm, type, expr)
                }else{
                    CompileUtil[type](node, this.vm, expr )
                }
            }
        })

    }

    // 解析文本节点
    compileText(node){
        let txt = node.textContent;
        let reg = /\{\{(.+)\}\}/;
        if(reg.test(txt)){
            let expr = RegExp.$1;
            node.textContent = txt.replace(reg, this.vm.$data[expr]);
        }       
    }


    /* 工具方法 */
    //转换数组方法
    toArray(likeArray){
        return [].slice.call(likeArray);
    }
    // 判断是否元素
    isElementNode(node){
        return node.nodeType === 1;
    }
    // 判断是否文本节点
    isTextNode(node){
        return node.nodeType === 3;
    }
    // 判断是否为指令
    isDirective(attrName){
        return attrName.startsWith('v-');
    }
    // 
    isEventDirective(type){
        return type.split(':')[0] === "on";
    }
}


let CompileUtil = {
    // 处理v-text指令
    text(node, vm, expr){
        node.textContent = vm.$data[expr];
    },
    html(node, vm, expr){
        node.innerHTML = vm.$data[expr];
    },
    model(node, vm, expr){
        node.value = vm.$data[expr];
    },
    eventHandler(node, vm, type, expr){
        //  给当前元素注册事件即可
        let eventType = type.split(":")[1];
        let fn = vm.$methods && vm.$methods[expr]
        if(eventType && fn){
            node.addEventListener(eventType, fn.bind(vm));
        }       
    }

}

解析vue中差值表达式和一些基础指令;学习总结如下:

1.fragment文档片段,内存中存在的元素,不会画到页面上,影响性能;

2.一些基础JS原生方法: 

  1. let fragment = document.createDocumentFragment();//创建fragment
  2. let childNodes = node.childNodes;//获取所有的子节点

  3. let attributes = node.attributes;//获取当前节点下所有的属性

  4. 正则的分组RegExp.$1,和reg.test()方法

3.Compile专门负责解析模板中的内容

你可能感兴趣的:(JS)