SOURCE

// 我们在使用Vue的时候往往会这样初始化
// let vm = new Vue({
//     el: '#app',
//     data: {
//         man: {
//             name: 'chenying',
//             age: '23',
//         }
//     }
// })
/** 不难看出Vue是一个类,接受一个options, 也就是钩子的集合,我们来实现一下这个类
我们会将options中的钩子挂载到Vue的实例上, 
这里只挂载了我们需要的$el和$data供后序流程使用, 然后通过new Compiler(this.$el, this)来进行编译
**/
class Vue {
    constructor(options) {
        this.$el = options.el
        this.$data = options.data

        if (this.$el) { // 如果元素存在 我们需要编译模板
            new Compiler(this.$el, this)
        }
    }
}
// Compiler类

/**
 * 首先会判断当前$el上面的值是节点还是字符串, 如果是字符串的话使用document.querySelector获取真实的DOM

然后调用nodeToFragment()方法递归遍历这个DOM节点, 将它存在文档碎片, 也就是内存中。

最后调用complie()方法对文档碎片进行编译,然后将编译后的文档随便追加到页面中。

我们可以很容易的猜到complie()方法的作用就是用options中的数据替换掉Vue语法中的{ {xx} } 和v-xx等值或者逻辑
 * 
 */

class Compiler {
    constructor(el, vm) {
        this.el = this.isElemntNode(el) ? el : document.querySelector(el)
        this.vm = vm

        // 将当前节点的元素全部丢到内存中(源码是通过ast)
        let fragment = this.nodeToFragment(this.el)

        // 编译模板,用数据替换
        this.compile(fragment)

        // 将内存的节点放回到页面中

        this.el.appendChild(fragment)
    }

    // 判断是否是node节点
    isElemntNode(node) {
        return node.nodeType === 1
    }
    // 蒋元素丢到文档碎片中
    nodeToFragment(node) {
        let fragment = document.createDocumentFragment()
        let firstChild
        while (firstChild = node.firstChild) {
            fragment.appendChild(firstChild)
        }
        return fragment
    }
    // 将内存中的节点编译
    compile(node) {
        let childNodes = node.childNodes
        const childs = [...childNodes]
        childs.forEach(child => {
            // 如果是节点就走节点的编译 否则就走文本的编译
            if (this.isElemntNode(child)) {
                this.compileElement(child)
            } else {
                this.compileText(child)
            }
        })
    }

    // 判断是否是指令 v-xxx
    isDirective(attrName) {
        return attrName.startsWith('v-')
    }

    // 编译文本
    compileText(node) {
        const content = node.textContent
        // 正则匹配 {{ xxx }} 的文本节点
        if (/\{\{(.+?)\}\}/.test(content)) {
            CompilerUnit['text'](node, content, this.vm)
        }
    }

    // 编译节点
    compileElement(node) {
        this.compile(node) // 如果是节点,节点内部的节点或元素也要编译

        const attributes = node.attributes // 获取标签上的属性
        // Array.from()、[...xxx]、[].slice.call 等都可以将类数组转化为真实数组
        const attrs = [...attributes]

        attrs.forEach(attr => {
            // attr格式:type="text"  v-modal="obj.name"
            const { name, value: expr } = attr
            if (this.isDirective(name)) {
                // name格式: v-model v-htmld
                let [, directive] = name.split('-') // 拿到 model html 等
                CompilerUnit[directive](node, expr, this.vm) // 不同指令走不同的处理函数
            }
        })

    }
}

const CompilerUnit = {
    // 根据 man.name 拿到 $data 里面的name的值
    getVal(vm, expr) {
        return expr.split('.').reduce((data, current) => {
            return data[current]
        }, vm.$data)
    },
    // 指令的处理
    model(node, expr, vm) {
        const fn = this.updata['modeUpdata']
        let value = this.getVal(vm, expr)
        fn(node, value)
    },
    // 文本的处理
    text(node, expr, vm) {
        const fn = this.updata['textUpdata']
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(vm, args[1].trim())  // 去除首尾空格 兼容多种写法 {{x}} {{ x }}
        })
        fn(node, content)
    },
    // 更新页面数据的方法集合
    updata: {
        // 更新指令
        modeUpdata(node, newValue) {
            node.value = newValue
        },
        // 更新文本
        textUpdata(node, newValue) {
            node.textContent = newValue
        }
    }

}












console 命令行工具 X clear

                    
>
console