Vue双向绑定

Vue双向绑定

Obejct.definePropertysetter/getter和发布订阅

Vue双向绑定原理

  • 1.实现一个数据监听器Observer(),能够对数据对象的所有属性进行监听,如有变动可以拿到最新值并通知订阅者
  • 2.实现一个指令解析器Compile(),对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  • 3.实现一个Watcher(),作为连接ObserverCompile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
    Vue双向绑定_第1张图片

Vue双向绑定实现

1.实现Observer

  • 利用Obeject.defineProperty()来监听属性变动
  • 需要将observer的数据进行递归遍历,包括子属性对象的属性,都加上settergetter
  • 给某个对象赋值,就会触发setter,那么就能监听到数据变化,通过notify()发布出去
    function observer(data, vm){
        if(typeof data !== 'object') return

        Object.keys(data).forEach(function(key){
            defineReactive(vm, key, data[key])
        })
    }

    function defineReactive(obj, key ,val){
        var dep = new Dep()
        Object.defineProperty(obj, key, {
            get: function(){
                // alert('属性监听 get '+Dep.target)
                // // Watcher的实例调用了getter 添加订阅者watcher
                if(Dep.target) dep.addSub(Dep.target)
                    return val
            },
            set: function(newVal){
                // alert('属性监听 set'+newVal)
                if(newVal === val){
                    return
                }else{
                    val = newVal
                    //作为发布者发出通知
                    dep.notify()
                }
            }
        })
    }

这样我们已经可以监听每个数据的变化了,那么监听到变化之后就是怎么通知订阅者了,所以接下来我们需要实现一个消息订阅器,很简单,就是一个数组用来收集订阅者,数据变动触发notify,再调用订阅者的update方法

    function Dep(){
        //定义subs数组存储watcher
        this.subs = []
    }

    Dep.prototype.addSub = function(sub){
        this.subs.push(sub)
    }

    Dep.prototype.notify = function(){
        this.subs.forEach(function(sub){
            sub.update()
        })
    }

2.实现Compile

  • compile主要是解析模板指令,将模板的变量替换成数据,然后初始化渲染页面视图
  • 并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变化,收到通知,更新视图


    Vue双向绑定_第2张图片
    function compile(node, vm){
        //指令 v- 模板引擎 {{}}
        var reg = /\{\{(.*)\}\}/;
        // 判断节点类型 nodeType  元素1  文本3
        if(node.nodeType === 1){
            var attr = node.attributes
            for(var i = 0; i < attr.length; i++){
                if(attr[i].nodeName == 'v-model'){
                    var name = attr[i].nodeValue//获取v-model绑定的属性名
                    // console.log(name)//text
                    node.addEventListener('input', function(e){
                        console.log(vm)
                        vm[name] = e.target.value
                    })
                    // 给相应的data属性赋值,触发该属性的set方法
                    node.value = vm.data[name]  //将data值赋值给node
                    node.removeAttribute('v-model')

                }
            }
        }

        //节点类型是文本
        if(node.nodeType === 3){
            if(reg.test(node.nodeValue)){
                var name = RegExp.$1 //获取过来{{text}} =>text
                name = name.trim()//trim
                // node.nodeValue = vm.data[name] //将data.text => {{text}}
                new Watcher(vm, node, name); //这里改成订阅者形式,
            }
        }

    }

3.实现Watcher

Watcher订阅者为ObserverCompile之间通信的桥梁,主要做的事

  • Compile中实例化时
  • 往属性订阅器(dep)里添加在自己
  • 自身必须有一个update()方法
  • 当数据变动是接到dep属性订阅器的notify发布通知时,能够调用自身的update()方法,从而触发get方法去更新数据
    //定义wactch
    function Watcher(vm, node, name){
        // console.log(this)
        this.vm = vm
        this.node = node
        this.name = name
        this.update()
    }

    Watcher.prototype = {
        update: function () {
            this.get();
            this.node.nodeValue = this.value;
        },
        // 获取data中的属性值 加入到dep中
        get: function () {
            Dep.target = this
            this.value = this.vm[this.name]; 
            Dep.target = null

        }
    }

Vue双向绑定效果

Vue双向绑定_第3张图片
vue双向绑定

完整代码

HTML

    
{{ text }}

你可能感兴趣的:(Vue双向绑定)