vue中mvvm实现方式探讨

49 阅读1分钟

经常在面试中被问到,vue的双向绑定是如何实现的?今天自己来实现一个简易的vue,来深入理解下vue的实现思路。

数据显示到页面

首先 把vue中传入的数据,显示到html页面

<div id="vue">
        <p>名称:{{name.name}}</p>
        <p>年龄{{age}}</p>
    </div>

    <script>
         new MVue({
            el: '#vue',
            data: {
                name:{
                    name:'张三'
                } ,
                age: '12'
            }
        })


    </script>
class MVue {
            constructor(options) {
                this.$options = options
                const node = document.querySelector(this.$options.el)
                this.complie(node)
            }

            complie(node) {
                Array.from(node.childNodes).forEach(element => {
                    const reg = /\{\{(.*?)\}\}/g
                    if (element.nodeType === 3 && reg.test(element.textContent)) {
                        const fieldNameArray = RegExp.$1.split('.') //当出现a.b的情况处理
                        let val=this.$options.data
                        fieldNameArray.forEach(fieldName=>{
                            val=val[fieldName]
                        })
                        element.textContent = element.textContent.replace(reg, val).trim();
                    }
                    // 如果还有子节点,继续递归replace
                    if (element.childNodes && element.childNodes.length) {
                        this.complie(element);
                    }
                })

            }
        }

数据的响应式

直接上代码

   class MVue {
        constructor(options) {
            this.$options = options
            const node = document.querySelector(this.$options.el)
            this.observe(options.data)
            this.complie(node)
            Object.keys(this.$options.computed).forEach(key => {

            })
        }

        observe(data) {

            Object.keys(data).forEach(key => {
                const subs = new Observer()
                data['_' + key] = data[key]
                Object.defineProperty(data, key, {
                    get() { // 获取值的时候 将对应的观察者对象
                        Observer.target && subs.addSubs(Observer.target)
                        return data['_' + key]
                    },
                    set(newValue) { // 通知对应的观察者对象 修改页面中的值
                        subs.notifySub(newValue)

                    }
                })
            })

        }

        complie(node) {


            Array.from(node.childNodes).forEach(element => {
                const reg = /\{\{(.*?)\}\}/g
                if (element.nodeType === 3 && reg.test(element.textContent)) {
                    const fieldName = RegExp.$1
                    /* let val = this.$options.data
                     fieldNameArray.forEach(fieldName => {
                         val = val[fieldName]
                     })*/
                    Observer.target = element
                    this.$options.data[fieldName]
                    Observer.target = null
                    element.textContent = element.textContent.replace(reg, this.$options.data["_" + fieldName]).trim();
                }
                // 如果还有子节点,继续递归replace
                if (element.childNodes && element.childNodes.length) {
                    this.complie(element);
                }
            })

        }
    }
    
    // 观察者对象
    class Observer {
        constructor() {
            this.subNodes = []
        }

        addSubs(target) {
            this.subNodes.push(target)
        }

        notifySub(val) {
            this.subNodes.forEach(node => {
                node.textContent = val
            })
        }
    }

方法observe:

1、先对data对象进行for循环,使用object.definePrototype的get方法来设置观察者对象(每个变量对应的模板Node节点),当值改变是,使用set方法来通知观察对象,修改对应的值

2。每次for循环形成闭包,观察者对象一直保存在闭包中

方法complie: 主要用于查找{{}}形式的节点,并且将对应的字段改成字段的值。同时调用observe中的get方法,对属性设置观察则对象

数据代理

我们在使用vue的时候,在获取data中某个属性的值时,不是使用this.$options.data.a.b,经常使用this.a.d的简写形式。observe函数的优化方式如下

          observe(data) {

                Object.keys(data).forEach(key => {
                    const subs = new Observer()
                    data['_' + key] = data[key]
                    Object.defineProperty(data, key, {
                        get() { // 获取值的时候 将对应的观察者对象
                            Observer.target && subs.addSubs(Observer.target)
                            return data['_' + key]
                        },
                        set(newValue) { // 通知对应的观察者对象 修改页面中的值
                            subs.notifySub(newValue)

                        }
                    })
                    // 数据代理 将data值 直接代理到实例中
                    Object.defineProperty(this, key,{
                        get() { 
                             return data['_' + key]
                        },
                        set(newValue) {
                            data['_' + key]=newValue

                        }
                    })
                })

            }