MVVM框架介绍--简易mvvm框架(2) | 青训营笔记

84 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

Compile-解析指令的基本步骤

(核心方法)解析html标签 : compileElement(node)

  1. 获取到当前节点下的所有的属性
    调用 node.attributes (attributes可以获取一个对象中的一个属性,并且作为对象来调用,注意在这里要使用“[]”;attributes 属性返回指定节点属性的集合。你可以使用 length 属性确定属性的数量,然后你可以遍历所有的属性节点提取你想要的信息。 每个属性都是可用属性节点对象。)
    对象.attributes                //获得所有属性节点,返回一个数组(伪数组)

image.png

通过isDirective识别指令

 isDirective(attrName) {
        //命名以什么开头
        return attrName.startsWith('v-')
    }

解析Vue的指令 通过compileElement

  // 解析html标签
    compileElement(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-text指令
                if (type === "text") {
                    node.textContent = this.vm.$data[expr]
                }
                // 解析v-html 指令
                if (type === "html") {
                    node.innerHTML = this.vm.$data[expr]
                }
                // 解析v-model 指令
                if (type === "model") {
                    node.value = this.vm.$data[expr]
                }
                // 解析v-on指令
            }
        })


    }

补充

1.V-ON 实现

问题:里面this的指向是当前按钮 <button v-on:click="clickFn">v-on</button> 所以通过 node.addEventListener(eventType, this.vm.$methods[expr].bind(this.vm))改变this指向,同时因为data还没有挂载到vm实例,所以只能先使用 console.log(this.$data.msg)

 const vm = new Vue({
            el: "#app",
            data: {
                msg: 'hello vue',
                tag: '<h3>哈哈</h3>'
            },
            methods: {
                clickFn() {
                    // 在vue的methods 中this应该指向当前实例
                    console.log(this.$data.msg)
                }
            },
        },
        )

v-on具体实现:

   // 解析v-on指令
                if (this.isEventDirective(type)) {
                    let eventType = type.split(":")[1]
                    console.log(this.vm.$methods[expr])
                    node.addEventListener(eventType, this.vm.$methods[expr].bind(this.vm))
                }
   // 工具方法
    isEventDirective(type) {
        return type.split(":")[0] === "on"
    }

2.工具方法

    /*工具方法*/
    //类数组(NodeList,arguments)变成数组
    toArray(linkArray) {
        return [].slice.call(linkArray)
    }
    isElementNode(node) {
        //nodeType :节点类型 1:元素节点 3:文本节点
        return node.nodeType === 1
    }
    isTextNode(node) {
        return node.nodeType === 3
    }
    isDirective(attrName) {
        //命名以什么开头
        return attrName.startsWith('v-')
    }
    isEventDirective(type) {
        return type.split(":")[0] === "on"
    }

代码优化:

如果有更多的指令,再通过判断进行实现就会使代码复杂化难以维护。 创建一个 CompileUtil 对象,借助自定义构造函数创建 text html model eventHandler 函数 ,将判断语句里的方法抽取到这里

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]
        console.log(fn)
        if (eventType && fn) {
            node.addEventListener(eventType, fn.bind(vm))
        }


    }

}

CompileElement

    compileElement(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

                if (this.isEventDirective(type)) {
                    CompileUtil["eventHandler"](node, this.vm, type, expr)

                } else {
                    //当有这个方法的时候再调用就不会报错了

                    CompileUtil[type] && CompileUtil[type](node, this.vm, expr)
                }
            }
        })


    }

(核心方法)解析html标签 : compileText(node)

补充:(解析插值表达式)

正则表达式常用方法

  • 校验数据

test(字符串)

测试字符是否满足正则表达式规则,如果测试到有,则返回true;没有则返回flase
语法:正则表达式.test(字符串) 正则表达式提供的方法

search(正则表达式)

search() 方法执行正则表达式和 String 对象之间的一个搜索匹配。
语法:字符串.search(正则表达式) 字符串提供的方法

区别: .test()方法是正则表达式提供的,.search()是字符串提供的, .test()方法返回布尔值,search()返回下标

RegExp 是javascript中的一个内置对象。为正则表达式

RegExp.1RegExp的一个属性,指的是与正则表达式匹配的第一个子匹配(以括号为标志)字符串,以此类推,RegExp.1是RegExp的一个属性,**指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串**,以此类推,RegExp.2,RegExp.3..RegExp.3,..RegExp.99总共可以有99个匹配

显示复杂数据 (拿对象类型的数据)

要拿到data.car里面的数据

   data: {
                msg: 'hello vue',
                tag: '<h3>哈哈</h3>',
                car: {
                    brand: '大众',
                    color: 'yellow'
                }
            },
            methods: {
                clickFn() {
                    // 在vue的methods 中this应该指向当前实例
                    console.log(this.$data.msg)
                }
            },

调用正则表达式,通过RegExp.$1获取到里面的值,将mustache方法放到CompileUtil对象里面,getVMValue作用是获通过data = data[item]得到对象里的值

  mustache(node, vm) {
        let txt = node.textContent
        let reg = /\{\{(.+)\}\}/
        if (reg.test(txt)) {
            let expr = RegExp.$1
            node.textContent = txt.replace(reg, CompileUtil.getVMValue(vm, expr))
        }


    },
    
      getVMValue(vm, expr) {
        // 获取data中的数据
        let data = vm.$data
        expr.split('.').forEach(item => {
            data = data[item]
        })
        return data
    }