• 模拟Vue 中数据双向绑定


    一、原理

    如果使用Object.defineProperty,实现一个最简单的双向绑定其实很简单,只需如下:

    <script>
        var Vue = {};
        Object.defineProperty(Vue,'$data',{
            set(val){
                document.getElementById('vue-item').innerText = val
            }
        });
        document.addEventListener('keyup', function(e){
            Vue.$data = e.target.value
        })
    </script>

    上面这个demo就是vue双向绑定最简化的原理。

    二、替换元素

    想想我们使用vue时的规则

    new Vue({
        el:'app',
        data:{
            text:'hello world'
        }
    });

    写上页面结构:

    <div id = 'app'>
        <input type='text' v-model='text'>
        {{text}}
    </div>

    我们把Vue抽象为一个构造函数,传入这些值

    function Vue(options){
            this.data = options.data;
            this.id = options.el;
            getAllNode(document.getElementById(this.id), this);
    };

    替换掉节点中所有的{{xxxx}}:

    function compile (node, vm){
            var reg = /{{(.*)}}/;// 匹配{{}}内任意字符
            // 节点类型为元素
            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;// 获取绑定的属性的名字
                        node.value = vm.data[name];// 替换值
                        node.removeAttribute('v-model');  //移除v-model
                    }
                };
            }
            // 节点类型为text
            if(node.nodeType === 3) {
                if(reg.test(node.nodeValue)) {
                    var name = RegExp.$1; //匹配到的第一个字符
                    name = name.trim();
                    node.nodeValue = vm.data[name]; // 将data的值赋值给node
                }
            }
        };
    
        function getAllNode(node, vm){
            var length = node.childNodes.length;
            for(var i = 0; i < length; i++){
                compile(node.childNodes[i], vm)
            }
        };

    这样就可以成功替换掉{{}}:

    三、绑定元素

    上面我们只是替换了元素,但还没有实现绑定

    实现数据绑定,就要用到definedProperty的set和get方法:

    首先我们要给vue的所有属性都添加set和get方法:

    function Vue(){
        // *****
        observe(data,this)
    }
    
    // 遍历
    function observe (obj, vm){
        for(var key in obj){
            active(vm, key, obj[key]);
        }
    }
    // 添加set和get
    function active (obj, key, val) {
        Object.defineProperty(obj, key, {
            get(){
                return val;
            },
            set(newVal){
                if (newVal === val){ return };
                val = newVal;
            }
        });
    }

    再来明确我们要做的事,获取输入的值,改变Vue中相应的data的值,同时改变{{}}中的值;

    我们已经给data的每个属性都添加了get和set的方法,现在要做的就是如何触发它们。

    触发它肯定是在赋值的时候,所以我们在有v-model属性的节点监听输入事件,同时赋值,触发set事件:

    function compile (node, vm){
        // ***********
        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;// 获取绑定的属性的名字

    // 监听input事件 node.addEventListener('input', function(e){ // 给相应的data属性赋值,触发set vm.data[name] = e.target.value }) node.value = vm.data[name];// 替换输入框的值为data中的值 node.removeAttribute('v-model'); } }; } // ************ }

    我们监听了input事件,接下来要获取输入的值并同步改变文本;

    我们肯定希望只希望哪里改变了就对哪里做处理就行了,所以我们引入一个简单的发布——订阅组件:

    function pubsub(){
        this.subs = []
    }
    
    pubsub.prototype = {
        addSub: function(sub){
            this.subs.push(sub);
        },
        pub: function(){
            this.subs.forEach(function(sub){
                sub.update();
            })
        }
    }

    在添加set和get的同时订阅事件:

    function active (obj, key, val) {
    
        var pubsub = new pubsub();
    
        Object.defineProperty(obj.data, key, {
            get(){
                // 添加订阅
                if(Pubsub.target){
                    pubsub.addSub(Pubsub.target);
                }
                return val;
            },
            set(newVal){
                if (newVal === val){ return };
                val = newVal;
    
                // 发出通知
                pubsub.pub();
            }
        });
    }

    添加一个方法,来在pubsub发出通知时处理事件,我们命名为watcher:

    function Watcher(vm, node, name){
            Pubsub.target = this;
            this.name = name;
            this.node = node;
            this.vm = vm;
            this.update();
            Pubsub.target = null
        }
    
    Watcher.prototype = {
          update(){
              this.get();
              this.node.nodeValue = this.value
          },
          // 获取data中的属性值
          get(){
              this.value = this.vm[this.name] // 触发相应的get
          }
      }
    
    function getAllNode(node, vm){ var length = node.childNodes.length; for(var i = 0; i < length; i++){ compile(node.childNodes[i], vm) } };

    这个watcher我们在什么时候添加呢?当然是在一开始的时候(compile里):

    function compile (node, vm){
        var reg = /{{(.*)}}/;// 匹配{{}}内任意字符
        // 节点类型为元素
        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;// 获取绑定的属性的名字
    
                    node.addEventListener('input', function(e){
                        // 给相应的data属性赋值,触发set
                        vm.data[name] = e.target.value
                    })
    
                    node.value = vm.data[name];// 替换输入框的值为data中的值
                    node.removeAttribute('v-model');
                }
            };
        };
        // 节点类型为text
        if(node.nodeType === 3) {
            if(reg.test(node.nodeValue)) {
                var name = RegExp.$1; //匹配到的第一个字符
                name = name.trim();
    
                // node.nodeValue = vm[name];
    
                new Watcher(vm, node, name);// 观察输入的值
            }
        }
    };

    至此,便模拟了整个数据绑定的流程

    四、总结

    最后理清整个过程的思路

    创建Vue:

    input事件:

  • 相关阅读:
    phonegap
    iOS8以前与iOS8使用CoreLocation定位
    phonegap调用摄像头
    js与nativede 通信
    大数据基础---Spring+Mybatis+Phoenix整合
    大数据基础---Hbase的SQL中间层_Phoenix
    大数据基础---Hbase容灾与备份
    大数据基础---Hbase协处理器详解
    大数据基础---Hbase 过滤器详解
    大数据基础---Hive数据查询详解
  • 原文地址:https://www.cnblogs.com/lastnigtic/p/6803854.html
Copyright © 2020-2023  润新知