Vue实现数据双向绑定

1、Vue实现数据双向绑定的总体概述

首先来说,什么是双向绑定?双向绑定总共包含两个方面:
一是数据变化视图更新,即响应式,实现的方式是:进行数据绑定(响应式)。
二是将视图变化数据修改,实现的方式是:DOM 事件监听
这两个方面都实现的,我们称之为数据的双向绑定

实现双向绑定,事件监听就不说了,难的是在于数据的绑定, vue数据绑定(响应式) 通过 “数据劫持“ + 订阅发布模式 二者相结合实现的,而数据劫持则是通过 Object.defineProperty()来重写get、set方法来实现。

2、Vue双向绑定详细过程说明----Observer、Compile、Watcher
  • 2.1 实现监听器Observer
    对于需要监听的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq';     // 哈哈哈,监听到值变化了 kindeng --> dmq

//遍历所有属性
function observe(data) {      
    if (!data || typeof data !== 'object') {
        return;
    }
   Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);      //添加get、set方法
    });
};

// 为每个属性添加get 、set 方法
function defineReactive(data, key, val) {
    var dep = new Dep();
    observe(val); // 监听子属性(也就是说如果子属性也是一个对象的话,继续为其添加get、set方法,以作监听)
    Object.defineProperty(data, key, {
        enumerable: true, // 可枚举
        configurable: false, // 不能再define
        get: function() {
            return val;
        },
        set: function(newVal) {
             if (val === newVal) return;
            val = newVal;
            dep.notify(); // 通知所有订阅者
        }
    });
}

//记录并通知订阅者
function Dep() {
    this.subs = [];
}
Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update(); // 调用订阅者的update方法,通知变化
        });
    }
};


//这样我们已经可以监听每个数据的变化了,那么监听到变化之后就是怎么通知订阅者了,
//所以接下来我们需要实现一个消息订阅器,很简单,维护一个数组,用来收集订阅者,
//数据变动触发notify,再调用订阅者的update方法,代码改善之后是这样:
  • 2.2、对template模版解析Compile
    compile主要做的事情是解析模板指令
    • 将模板中的变量替换成数据,然后初始化渲染页面视图,
    • 将每个指令对应的节点绑定更新函数,
    • 添加数据的订阅者,一旦数据有变动,收到通知,更新视图
function Compile(el) {
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    if (this.$el) {
        this.$fragment = this.node2Fragment(this.$el);
        this.init();
        this.$el.appendChild(this.$fragment);
    }
}
Compile.prototype = {
    init: function() { this.compileElement(this.$fragment); },
    node2Fragment: function(el) {
        var fragment = document.createDocumentFragment(), child;
        // 将原生节点拷贝到fragment
        while (child = el.firstChild) {
            fragment.appendChild(child);
        }
        return fragment;
    }
};
  • 2.3、实现Watcher
    Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
    ①在自身实例化时往属性订阅器(dep)里面添加自己
    ②自身必须有一个update()方法
    ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调。
function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    // 此处为了触发属性的getter,从而在dep添加自己,结合Observer更易理解
    this.value = this.get(); 
}
Watcher.prototype = {
    update: function() {
        this.run();    // 属性值变化收到通知
    },
    run: function() {
        var value = this.get(); // 取到最新值
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal); // 执行Compile中绑定的回调,更新视图
        }
    },
    get: function() {
        Dep.target = this;    // 将当前订阅者指向自己
        var value = this.vm[exp];    // 触发getter,添加自己到属性订阅器中
        Dep.target = null;    // 添加完毕,重置
        return value;
    }
};
  • 2.4、最终通过 Observer、Compile 和 Watcher三者实现双向绑定

    通过 Observer 来监听自己的 model 数据变化,

    通过 Compile来解析编译模板指令,

    最终利用 Watcher 搭起 Observer 和 Compile之间的通信桥梁,

    达到数据变化 -> 视图更新;视图交互变化(input)-> 数据 model 变更的双向绑定效果。
    在这里插入图片描述

3、数据劫持
  • 是什么
    指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果

  • 实现的方式
    1、Object.defineProperty(),重写get和set方法
    2、利用es6中Proxy对象

    vue2.x使用Object.defineProperty();
    vue3.x使用Proxy;

4、 Object.defineProperty 有什么缺陷
  • 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;为了解决这个问题,vue内部重写了数组常用的方法:
    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()
  • 需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。而Proxy可以劫持整个对象,并返回一个新的对象。
5、简化版双向绑定的实现案例
<body>
   <div id="app">
       <input type="text" id="txt">
       <p id="show-txt"></p>
   </div>
   <script>
       var obj = {}
       Object.defineProperty(obj, 'txt', {
           get: function () {
               return obj
           },
           set: function (newValue) {
               document.getElementById('txt').value = newValue
               document.getElementById('show-txt').innerHTML = newValue
           }
       })
       document.addEventListener('keyup', function (e) {
           obj.txt = e.target.value
       })
   </script>
</body>

参考链接 https://www.cnblogs.com/alongup/p/9022180.html
参考链接 https://www.chuchur.com/article/vue-mvvm-observer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值