Vue MVVM理解及实现

MVVM

简单地说就是数据驱动视图,视图改变(事件)也可以改变数据,就是双向绑定的概念。

实现

为了监听数据的改变,从而响应到视图上,用的是Vue双向绑定的核心Object.definePrototype(obj,key,{...}),编写Observer.js来实现。若set被触发,通知所有的订阅者(dep中存储的是所有订阅者实例对象),且在get中引入Dep.target,仅在添加watcher时主动赋值,防止之后多次添加watcher,并且get返回当前的val值。
Object.defineProperty(data,key,{
        enumerable: true, // 可枚举,可被Object.value()等遍历方法所遍历
        configurable: true, // 可再重新定义,即可被修改,可被删除, 默认为false
        get: function(){
            // console.log(Dep.target)
            // Dep.target现在其实为null,但是在watcher.js中每次get都将watch自身添加到全局的Dep.target中
            // 等到value获取完毕,再将Dep.target清空
            // Dep.target作为闭包,在函数中保持了dep的存在
            // 如果没有Dep.target,dep会被清除,在set中就无法通过dep.nptify()来出发watcher了
            // Dep.target && dep.addSub(Dep.target)
            if(Dep.target){
                dep.addSub(Dep.target) // 添加一个订阅者
            }
            return val
        },
        set: function(newVal){
            console.log('值变化')
            if(newVal === val) return
            val = newVal
            // 通知所有订阅者
            dep.notify()
        }
    })
订阅者watcher.js关联着模板编译,每个生成的订阅者都包含一个修改模板的callback,一旦对应发布者Observer.js中的set函数执行,所有对应订阅者接收到通知,就会执行该订阅者被创建时包含的callback,修改视图。
    update: function(){
        this.run() // 属性值变化收到通知
    },
    run: function(){
        // 数据改变时
        var value = this.vm.data[this.exp] // 取到最新的值 || this.get()
        var oldVal = this.value // 存储老值
        if(value !== oldVal){
            this.value = value
            this.cb.call(this.vm,value,oldVal) // 执行compile中的回调,更新视图
        }
    }
在Vue中实现数据绑定有两种途径,一种是双大括号{{}},一种是v-model,为了将数据绑定到页面,同时为了给包含这两种情况的模板添加订阅者,编写compile.js来实现。compile.js接收MVVM实例中挂载的根节点和该实例对象。先将所有节点剪切到fragment文档片段中,再通过遍历所有节点的方式,碰到包含数据绑定的节点,就创建一个新的watcher,并包含改变视图的callback从而与watcher.js关联,碰到其他类似事件绑定的节点,则给其绑定事件监听器,从而实现v-on的事件绑定效果。另外使用文档片段的好处是避免了页面的频繁的回流重绘,文档节点使用完毕后返回页面只需渲染一次即可。

核心代码

    init(){
        if(this.el){
            this.fragment = this.nodeToFragment(this.el) 
            this.compileElement(this.fragment);
            this.el.appendChild(this.fragment);
        }
    },
    // 节点全部转为文档片段
    nodeToFragment(el){
        // 创建空的文档片段
        var fragment = document.createDocumentFragment()
        var child = el.firstChild
        while(child){
            // 子节点推入文档片段,appendChild会有剪切的效果!!!!!
            fragment.appendChild(child)
            child = el.firstChild
        }
        return fragment
    },
    compileElement(el){
        // 创建好的文档片段拿过来编译
        var childNodes = el.childNodes
        var self = this
        // dom数组不是真正的数组,没有遍历方法

        // 多层嵌套slice处理效率过低导致执行失效
        // [].slice.call(childNodes).forEach(function(node){})
        // 也好像不是????在控制台测试了一下都运行的飞快啊...
        Array.prototype.forEach.call(childNodes,function(node){
            // 处理{{}}的正则
            var reg = /\{\{(.*)\}\}/
            var text = node.textContent

            if(self.isElementNode(node)){
                self.compile(node)
            }else if(self.isTextNode(node) && reg.test(text)){
                // 检测到双括号
                self.compileText(node,reg.exec(text)[1])
            }

            // 递归编译,编译所有节点
            if(node.childNodes && node.childNodes.length){
                self.compileElement(node)
            }
        })
    }

参考链接

canfoo

你可能感兴趣的:(Vue MVVM理解及实现)