Vue 2.x MVVM实现原理

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .ulclass {
            border: 1px solid #ccc;
        }
        #ulclass {
            color: red;
        }
    </style>
</head>
<body>
    <div id="app">
        <input type="text" v-model="name">
        <p  :id="cls"> {{age}}</p>
        <div>姓名:{{name}} 年龄:{{age}}  我是{{user.gionlee.name}}</div>
        <ul v-bind:class="cls">
            <li v-text="user.gionlee.name">1</li>
            <li v-html="user.gionlee.html">2</li>
            <li v-text="name">3</li>
        </ul>
        <button  v-on:click="test">v-on点击</button>
        <button @click="test">@点击</button>
    </div>
</body>
<script src="./observer.js"></script>
<script src="./mvvm.js"></script>
<script>
    let vm = new MvvM({
        el:'#app',
        data :{
            cls:'ulclass',
            text:'111',
            name:'admin',
            age:18,
            user:{
                gionlee:{
                    name:"123",
                    html:"<p>my name is gionlee!</p>"
                }
            }
        },
        methods: {
            test () {

                console.log(this,'事件绑定成功!')
            }
        }
    })
</script>
</html>

js:

mvvm.js

const compileUtil = {
    // 深度获取对象的值如: user.name => 'gionlee'
    getValue (expr,vm) {
        return expr.split('.').reduce( (data,val) => {
            return data[val]
        },vm.$data)
    },
    setValue (expr,vm,value) {
        return expr.split('.').reduce( (data,val) => {
            if(typeof data[val] != 'object') {
                return data[val] = value
            }
            return data[val]
        },vm.$data)
    },
    // 获取文本对象  批量处理 单个标签内使用多个变量如: <div>姓名:{{name}}年龄:{{age}}</div>
    getContext(expr,vm) {
        const value = expr.replace(/\{\{(.+?)\}\}/g, (...arg) => {
            return this.getValue(arg[1],vm)
        })
        return value
    },
    // 渲染 v-text 、 {{name}}
    text(node,expr,vm) {
        let value;
        if(expr.indexOf('{{') !== -1) {
            value = expr.replace(/\{\{(.+?)\}\}/g, (...arg) => {
                new Watcher (vm,arg[1], (newValue) => {
                    this.updater.textUpdater(node,this.getContext(expr,vm))
                })
                return this.getValue(arg[1],vm)
            })
        } else {
            value = this.getValue(expr,vm)
            new Watcher (vm,expr, (newValue) => {
                this.updater.textUpdater(node,newValue)
            })
        }
        this.updater.textUpdater(node,value)
        
    },
    // 渲染 v-html
    html(node,expr,vm) {
        const value = this.getValue(expr,vm)
        new Watcher (vm,expr, (newValue) => {
            this.updater.htmlUpdater(node,newValue)
        })
        this.updater.htmlUpdater(node,value)
    },
    // 渲染 v-model
    model(node,expr,vm) {
        const value = this.getValue(expr,vm)
        // console.log(value)
        new Watcher (vm,expr, (newValue) => {
            this.updater.modelUpdater(node,newValue)
        })
        // 输入时对v-model绑定的值进行更新
        node.addEventListener('input',(e)=> {
            this.setValue(expr,vm,e.target.value)
        })
        this.updater.modelUpdater(node,value)
    },
    // 绑定事件
    on(node,expr,vm,event){
        let fn = vm.$options.methods && vm.$options.methods[expr]
        node.addEventListener(event,fn.bind(vm),false)
    },
    //  渲染  v-bind
    bind(node,expr,vm,attrName) {
        node.setAttribute(attrName,this.getValue(expr,vm))
    },
    // 更新函数
    updater: {
        
        textUpdater(node,value) {
            node.textContent = value
        },
        htmlUpdater(node,value) {
            node.innerHTML = value
        },
        // 更新 v-model 绑定的变量
        modelUpdater(node,value) {
            node.value = value
        }
    }
}
class MvvM {
    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)
        }
    }
    // 代理this 
    proxyData(data) {
        for(const key in data) {
            Object.defineProperty(this,key,{
                get() {
                    return data[key]
                },
                set(newValue) {
                    data[key] = newValue
                }
            })
        }
    }

}
class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm
        // 获取文档碎片对象  放入内存  减少页面的回流和重绘
        const fragment = this.nodeToFragment(this.el)
        // 编译模板
        this.compile(fragment)
        // 追加子元素到根元素
        this.el.appendChild(fragment)
    }
    isElementNode(node) {
        return node.nodeType === 1;
    }
    compile(fragment) {
        // 获取子节点
        const childNodes = fragment.childNodes
        childNodes.forEach(element => {
            if (this.isElementNode(element)) {
                // 编译元素节点
                this.compileElement(element)
            } else {
                // 编译文本节点
                this.compileText(element)
            }
            // 递归遍历节点
            if (element.childNodes && element.childNodes.length) {
                this.compile(element)
            }
        });
    }
    compileElement(node) {
        const attributes = node.attributes
        if (attributes && attributes.length) {
            [...attributes].map((attr,index) => {
                const {name,value} = attr
                if(this.isDirective(name)) {
                    const [,directive] = name.split('-')
                    const [dirName,eventName] = directive.split(':')
                    // 更新数据
                    compileUtil[dirName](node,value,this.vm,eventName)
                    // 删除指令属性
                    node.removeAttribute('v-' + directive)
                } else if (this.isEventName(name)) {    // 判断事件绑定 使用@时按照v-on处理
                    let [,eventName] = name.split('@')
                    compileUtil['on'](node,value,this.vm,eventName)
                } else if (this.isAttrName(name)) {  // 使用 :绑定属性时 按照v-bind处理
                    let [,attrName] = name.split(':')
                    node.removeAttribute(':' + attrName)
                    compileUtil['bind'](node,value,this.vm,attrName)
                }
            })
        }
    }
    compileText(node) {
        const content = node.textContent
        if(/\{\{(.+?)\}\}/.test(content)) {
            compileUtil['text'](node,content,this.vm)
        }
    }
    isAttrName(attrName) {
        return attrName.startsWith(':')
    }
    isEventName (eventName) {
        return eventName.startsWith('@')
    }
    isDirective (attrName) {
        return attrName.startsWith('v-')
    }
    nodeToFragment(el) {
        // 创建文档碎片
        const fragment = document.createDocumentFragment()
        let firstChild;
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild)
        }
        return fragment
    }
}

 

observer.js

// 观察者 监听数据变化
class Watcher {
    constructor (vm,expr,cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        this.oldValue = this.getOldValue()
    }
    getOldValue () {
        Dep.target = this
        const oldValue =  compileUtil.getValue(this.expr,this.vm)
        Dep.target = null
        return oldValue
    }   
    update () {
        console.log(this.expr)
        // 有变化更新视图
        const newValue = compileUtil.getValue(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( watcher => {
            watcher.update()
        })
    }
}
class Observer {
    constructor(data) {
        this.observe(data)
    }
    observe(data) {
        // 判断是否为对象
        if(data && typeof data === 'object') {
            Object.keys(data).forEach( (key) => {
                this.defineReactive(data,key,data[key])
            })
        }
    }
    defineReactive(obj,key,value) {
        // 递归遍历
        this.observe(value)
        const dep = new Dep()
        // 劫持并监听所有的属性
        Object.defineProperty(obj,key,{
            configurable:false,
            enumerable:true,
            get () {
                // 订阅的数据发生变化时,向Dep中添加观察者
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set :(newValue) => {
                this.observe(newValue)
                if(newValue !== value) {
                    value = newValue
                }
                //  通知Dep变化
                dep.notify()
            }
        })
        
    }
}

 

posted @ 2020-09-29 17:34  那时年少青衫薄。  阅读(157)  评论(0编辑  收藏  举报