vue响应式之MVVM的简单实现

1、首先写一些公用的方法

先写一些方法用来获取数据和封装好设置数据的逻辑

let utils = {
        getValue (data, key) {
            if (key.indexOf('.') > -1) {
                key.split('.').forEach(item => {
                    data = data[item]
                })
                
            } else {
                data = data[key]
            }
            return data
        },
        changeHtml (node, data, key) {
            node.textContent = utils.getValue(data, key)
        },
        setContent (node, data, key) {
            new Watcher(data, key,(val)=>{
                node.textContent = val
            })
            node.textContent = this.getValue(data, key)
        },
        setValue (data, key, val) {
            let newData = data;
            let newValues = key.split('.')
            if(key.indexOf('.') > -1) {
                for(let i = 0; i < newValues.length - 2; i++) {
                    newData = newData[newValues[i]]
                }
                newData[newData[newData.length - 1]] = val;
            } else {
                newData[key] = val
            }
        }
    }
2、我们写一个类用来监听属性变化

data是Vue实例的数据对象,当实例初始化时,Vue 会遍历 data 中的所有属性,并且使用 Object.definePropery 把这些属性全都转为 getter/setter ,从而让 data 的属性能够响应数据变化。另外,Object.defineProperty 是 ES5 中一个无法 shim(垫片) 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。对象必须是纯粹的对象 (含有零个或多个的 key/value 键值对):浏览器 API 创建的原生对象。所以,在data中声明过的数据是响应式数据,而在data外使用 Vue 实例增加的数据不属于响应式

class Observer {
        constructor (data) {
            if (!data || typeof data !== 'object') {  //判断当前参数是否是对象类型
                return
            }
            this.data = data;
            Object.keys(this.data).forEach(key => {
                this.observer(this.data, key, this.data[key])//取出所有的key值去设置监听
            })
        }
        observer (data, key, val) {
            let child = new Observer(data[key])//对每个当前属性值进行递归如果是对象则继续设置监听
            let dep = new Dep();//对每个属性实例一个监听器

            Object.defineProperty(data, key, {
                get () { //
                    if(Dep.target){ //判断是否是页面初始化的时候
                        dep.addSub(Dep.target);//如果是的话给当前属性监听器添加一个事件
                    }
                    console.log(dep)
                    return val 
                },
                set (newVal) { 
                    val = newVal //更改数据时将值设置为新值
                    child = new Observer(val) 
                    dep.doAll() //执行当前属性的
                    
                }
            })
        }
    }
3、然后写一个盛放某个属性所有回调的类

Dep将在每一个属性设置访问器的时候实例,页面中用到对应属性时会在Dep的数组中添加一个事件监听,当属性发生变化时会调用所有的监听里的回调函数从而实现重新渲染用到此数据的对应节点

 var uid = 0;
    class Dep {
        constructor () {
            this.id = uid++; //每个属性设置一个id防止多次重复添加
            this.listenFunc = [] //用于盛放所有的当前属性回调用于属性发生变化时更新视图
        }
        addSub (watcher) { //调用传入的watch中的方法将当前实例传入
            watcher.addFunc(this)
        }

        addFunc (watcher) {
            this.listenFunc.push(watcher) //将当前实例加入到属性回调数组
        }
        doAll () {
            this.listenFunc.forEach(item => { //属性变化时调用所有回调变化当前属性的视图
                item.resetHtml()
            })
        }
    }

    Dep.target = null; //先初始化一个属性后边会介绍到为什么用
4、下面我们写一个用于获取当前数据来更新视图的类

我们在页面中用到某个属性时会实例一个Watcher,利用闭包将它放到对应的Dep中,如果该属性发生了变化,就可以去通过watcher来更新视图了。

 class Watcher {
        constructor ( data,key, cb) {
            this.data = data
            this.key = key
            this.cb = cb
            this.depIds = {};
            this.resetHtml()  //实例时直接触发视图渲染
        }
        init () {

            Dep.target = this //此处与上边Dep.target呼应用于临时保存当前实例
            let value = utils.getValue(this.data, this.key ) //此处执行的获取当前属性值会触发获取的get,将当前实例添加到Dep实例的监听数组
            Dep.target = null //添加完成后清除 防止获取数据时添加无用回调
            return value
        }
        addFunc (dep) { 
            console.log(dep.id)
            if (!this.depIds.hasOwnProperty(dep.id)) {//看当前的id是否已经存在,存在的话不要反复添加
                dep.addFunc(this)
                this.depIds[dep.id] = dep;
            }
            
        }
        resetHtml () { //根据当前的数据重新调用回调渲染当前页面
            this.cb(this.init())

        }


    }

5、写我们MVVM实例时的一些逻辑和方法
class Mvvm {
        constructor (el, data) { 
                this.el = el;
                this.data = data.data;
                this.init();
                this.initDom();
        }

        init () {
            this.$el = document.querySelector(this.el);
            Object.keys(this.data).forEach(key => {
                this.observer(this.data, key, this.data[key]);
            })
            new Observer(this.data)
        }

        observer (data, key, val) {
            Object.defineProperty(data, key, {
                get () {
                    return val;
                },
                set (newVal) {
                    val = newVal
                }
            })
        }

        initDom () {
            let newFargment = this.creataFragment();
            this.compiler(newFargment);
            this.$el.appendChild(newFargment);
        }

        creataFragment () {
            let fragment = document.createDocumentFragment();
            let firstChaild;
            while(firstChaild = this.$el.firstChild){
                fragment.appendChild(firstChaild)
            }
            return fragment
        }
        compiler (node) {
            Array.from(node.childNodes).forEach(item => {
                if(item.nodeType === 1) {
                    let flag = false;
                    Array.from(item.attributes).forEach(val => {
                        if(val.nodeName === 'v-model') {
                            flag = val.nodeValue;
                        }
                    
                    })
                    if (flag){
                        item.value = utils.getValue(this.data, flag)
                        new Watcher(this.data, flag,(val)=>{
                            item.value = val
                        })
                        item.addEventListener('input', (e) => {
                            console.log(e.target.value)
                            utils.setValue(this.data, flag, e.target.value)
                        }, false)
                    }   
                    this.compiler(item)
                }else if (item.nodeType === 3) {
                    if (item.textContent.indexOf('{{') > -1 && item.textContent.indexOf('}}') > -1 && item.textContent.trim()) {
                        let key =  item.textContent.split('{{')[1].split('}}')[0];
                        utils.setContent(item, this.data, key)
                    }
                    
                }

                if (item.childNodes && item.childNodes.length > 0) {
                    item.childNodes.forEach(val => this.compiler(val))
                }

            })
            
        }

    }
6、实例我们的MVVM
 let mvvm = new Mvvm("#app", {
        data: {
            age: 12,
            aihao: {
                aa: 'aa',
                bb: 'bb'
            }
        }
    })
    
整个的流程梳理
1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

你可能感兴趣的:(vue)