美文网首页
Vue双向绑定

Vue双向绑定

作者: silly鸿 | 来源:发表于2018-02-27 20:40 被阅读0次

    Vue双向绑定

    Obejct.definePropertysetter/getter和发布订阅

    Vue双向绑定原理

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

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


        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双向绑定

    完整代码

    HTML

        <div id='app'>
            <input v-model='text'>
            {{ text }}
            <span></span>
        </div>
    
    <script src="vue.0.0.1.js"></script>
    <script >
    var app = new Vue({
        el: 'app',
        data: {
            text: "this is test text",
            message: {
                name: "weiong"
            }
        }
    })
    

    JS

    'use strict';
    (function(global, factory){
        global.Vue = factory();
    })(this, function(){
        var Vue = function(options){
            //挂载的节点
            var id = options.el||"body";
            // console.log(id)
    
            //模板数据
            this.data = options.data || {}
            var data = this.data
    
            //监听模板数据
            observer(data, this)
    
            //节点劫持到一个DOM容器
            var dom  = nodeTodocumentfragment(document.getElementById(id), this)
            //最后挂载
            document.getElementById(id).appendChild(dom)
        }
    
    
        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()
            })
        }
    
        //定义wactch
        function Watcher(vm, node, name){
            // console.log(this)
            this.vm = vm
            this.node = node
            this.name = name
            this.update()
        }
    
        Watcher.prototype = {
            update: function () {
                // alert('Watcher update')
                this.get();
                this.node.nodeValue = this.value;
            },
            // 获取data中的属性值 加入到dep中
            get: function () {
                // alert('Watcher get')
                Dep.target = this
    
                this.value = this.vm[this.name]; // 触发相应属性的getter,从而添加订阅者
                Dep.target = null
    
            }
        }
    
        //数据监听 obj是data对象
        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()
                    }
                }
            })
        }
    
        //节点劫持
        //documentfragment DOM 容器
        function nodeTodocumentfragment(obj, vm){
            var flag = document.createDocumentFragment()
            var child
            // appendChild 成功后,会把节点从原来的节点位置移除;
            // 中转站
            while(child = obj.firstChild){
                // console.log(child)
                // 扫描 节点劫持  model数据模板编译
                compile(child, vm)
                flag.appendChild(child)
            }
            // console.log(flag)
            return flag
        }
    
    
        // compile扫描每一个子节点
        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')
                        // alert('节点赋值')
    
                    }
                }
            }
    
    
            //节点类型是文本
            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}}
                    // alert('文本赋值 new Watcher')
                    new Watcher(vm, node, name); //这里改成订阅者形式,
                }
            }
    
        }
    
    
        return Vue
    })
    
    
    

    参考连接
    剖析Vue原理&实现双向绑定MVVM
    vue的双向绑定思想

    相关文章

      网友评论

          本文标题:Vue双向绑定

          本文链接:https://www.haomeiwen.com/subject/fyvmxftx.html