SOURCE

console 命令行工具 X clear

                    
>
console
// 各种类型指令的编译策略
const compileUtil = {
    getVal(expr, vm) {
        // person.name -> [person, name] -> vm.$data['person'] -> currentData['name']
        return expr.split('.').reduce((data, key) => data[key], vm.$data)
    },
    setVal(expr, vm, inputVal) {
        const propsArr = expr.split('.')
        const targetProp = propsArr.pop()
        const target = propsArr.reduce((data, key) => data[key], vm.$data)
        target[targetProp] = inputVal
    },
    getContentValue(expr, vm) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(args[1], vm)
        })
    },
    text(node, expr, vm) {
        // v-text="text" {{person.name}}
        let value
        if (expr.indexOf('{{') !== -1) {
            value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
                new Watcher(vm, args[1], () => {
                    this.updater.textUpdater(node, this.getContentValue(expr, vm))
                })
                return this.getVal(args[1], vm)
            })
        } else {
            value = this.getVal(expr, vm)
            new Watcher(vm, expr, (newValue) => {
                this.updater.textUpdater(node, newValue)
            })
        }
        this.updater.textUpdater(node, value)
    },
    html(node, expr, vm) {
        const value = this.getVal(expr, vm)
        new Watcher(vm, expr, (newValue) => {
            this.updater.htmlUpdater(node, newValue)
        })
        this.updater.htmlUpdater(node, value)
    },
    model(node, expr, vm) {
        const value = this.getVal(expr, vm)
        new Watcher(vm, expr, (newValue) => {
            this.updater.modelUpdater(node, newValue)
        })
        node.addEventListener('input', (event) => {
            this.setVal(expr, vm, event.target.value)
        })
        this.updater.modelUpdater(node, value)
    },
    on(node, expr, vm, eventName) {
        const fn = vm.$options.methods && vm.$options.methods[expr]
        node.addEventListener(eventName, fn.bind(vm))
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value
        },
        htmlUpdater(node, value) {
            node.innerHTML = value
        },
        modelUpdater(node, value) {
            node.value = value
        }
    }
}

// 数据观察者
class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        this.oldValue = this.getOldValue()
    }
    getOldValue() {
        Dep.target = this
        const value = compileUtil.getVal(this.expr, this.vm)
        Dep.target = null
        return value
    }
    update() {
        const newValue = compileUtil.getVal(this.expr, this.vm)
        if (newValue !== this.oldValue) {
            this.cb(newValue)
        }
    }
}

// 数据与视图的依赖收集器
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(watcher) {
        this.subs.push(watcher)
    }
    notify() {
        this.subs.forEach(w => {
            w.update()
        })
    }
}

// Observer 用于劫持数据,在 getter 中收集依赖,setter 触发依赖更新
class Observer {
    constructor(data) {
        this.observer(data)
    }
    observer(data) {
        if (data && typeof data === 'object') {
            Object.keys(data).forEach(key => {
                this.defineReactive(data, key, data[key])
            })
        }
    }
    defineReactive(obj, key, value) {
        this.observer(obj[key])
        const dep = new Dep()
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: false,
            get() {
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newValue) => {
                // 如果设置的是一个对象,需要重新观察
                this.observer(newValue)

                if (newValue !== value) {
                    value = newValue
                    dep.notify()
                }
            }
        })
    }
}

// 编译器,用于编译模板,解析指令
class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm
        // 将模板节点添加到文档碎片,减少性能消耗
        const fragment = this.node2Fragment(this.el)
        // 开始编译
        this.compile(fragment)
        this.el.appendChild(fragment)
    }
    compile(fragment) {
        const childNodes = fragment.childNodes
            // 遍历子节点,判断节点的类型执行不同的编译方式
            ;[...childNodes].forEach(child => {
                if (this.isElementNode(child)) {
                    this.compileElement(child)
                } else {
                    this.compileText(child)
                }
                // 递归遍历所有子节点
                if (child.childNodes && child.childNodes.length) {
                    this.compile(child)
                }
            })
    }
    // 编译 element 节点类型
    compileElement(node) {
        const attrs = node.attributes
            ;[...attrs].forEach(attr => {
                const { name, value } = attr
                if (this.isDirective(name)) {
                    //v-text v-html v-model v-on:click -> text html model on:click
                    const [, directive] = name.split('-')
                    // on:click -> on click
                    const [dirName, eventName] = directive.split(':')
                    compileUtil[dirName] && compileUtil[dirName](node, value, this.vm, eventName)
                    node.removeAttribute('v-' + directive)
                }
            })
    }
    // 编译文本类型
    compileText(node) {
        const content = node.textContent
        // {{person.name}}
        if (/\{\{(.+?)\}\}/.test(content)) {
            compileUtil.text(node, content, this.vm)
        }
    }
    isDirective(name) {
        return name.startsWith('v-')
    }
    node2Fragment(node) {
        const fragment = document.createDocumentFragment()
        let firstChild
        while (firstChild = node.firstChild) {
            fragment.appendChild(firstChild)
        }
        return fragment
    }
    isElementNode(node) {
        return node.nodeType === 1
    }
}
class MVue {
    constructor(options) {
        this.$el = options.el
        this.$data = options.data
        this.$options = options

        if (this.$el) {
            new Observer(this.$data)
            new Compile(this.$el, this)
            this.proxyData(this.$data)
        }
    }

    // 代理 data 数据,实现 this.xx 访问和修改数据
    proxyData(data) {
        Object.keys(data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set(newVal) {
                    data[key] = newVal
                }
            })
        })
    }
}




window.vueInstance = new MVue({
    el: '#app',
    data: {
        person: {
            name: 'Zhang San',
            age: 20,
            fav: 'Game'
        },
        htmlContent: `<em>User data:</em>`
    },
    methods: {
        handleClick() {
            this.person.name = 'Li Si'
        }
    }
})
<div id="app">
	<h1 v-text="person.name"></h1>
	<h4 v-html="htmlContent"></h4>
	<ul>
        <li>Age: {{person.age}}</li>
		<li>Fav: {{person.fav}}</li>
	</ul>
	<input type="text" v-model="person.name" />
    <button v-on:click="handleClick">changeName</button>
</div>