vue数据双向绑定原理-wather​


​​
1)vue数据双向绑定原理-observer
​2)vue数据双向绑定原理-wather​
3)vue数据双向绑定原理-解析器Complie

vue数据双向绑定原理, 和简单的实现,本文将实现mvvm的 Watcher

上面的步骤已经实现了监听器, 和订阅器, 当属性发生改变,发出通知, 那么这个通知 是通知谁呢,肯定是订阅者 watcher.
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:

1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则释放自己。

// Watcher
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[this.exp];    // 强制触发监听的getter,添加自己到属性订阅器中
        Dep.target = null;    // 添加完毕,重置释放
        return value;
    }
};

订阅者要缓存自己, 并且告诉监听器, 要把我加到订阅器里面去. 所以还要改造下 监听器

function defineReactive(data, key, val) {
    var dep = new Dep()
    observe(val); // 监听子属性
    Object.defineProperty(data, key, {
      .......
        get: function () {
            // 由于需要在闭包内添加watcher,所以可以在Dep定义一个全局target属性,暂存watcher, 添加完移除
            Dep.target && dep.addDep(Dep.target);
            return val;
        },
.......
    });
}

实例化Watcher的时候,调用get()方法,通过Dep.target = watcherInstance标记订阅者是当前watcher实例,强行触发属性定义的getter方法,getter方法执行的时候,就会在属性的订阅器dep添加当前watcher实例,从而在属性值有变化的时候,watcherInstance就能收到更新通知。

实现MVVM
到这儿先将监听器 Observer 和监听者Watcher 连起来, 先模拟一些数据,实现简单的数据绑定

<div id="name"></div>
<script>
function Vue(data, el, exp) {
   this.data = data;
   observe(data);
   el.innerHTML = this.data[exp]; // 初始化模板数据的值
   new Watcher(this, exp, function (value) {
      el.innerHTML = value;
   });
   return this;
}
var ele = document.querySelector('#name');
var vue = new Vue({
   name: 'hello world'
}, ele, 'name');
setInterval(function () {
   vue.data.name = 'chuchur ' + new Date() * 1
}, 1000)
</script>

这可以看到 div的和内容初始为 hello world , 每隔一秒之后变换为 chuchur 加时间戳,虽然是实现了, 但是与想象的还差很多. 是vue.name 不是 vue.data.name ,所以这里需要给Vue实例添加一个属性代理的方法,使访问vm的属性代理为访问vm.data的属性,改造后的代码如下:

function Vue(options) {
   this.$options = options || {};
   this.data = this.$options.data;
   // 属性代理,实现 vm.xxx -> vm.data.xxx
   var self = this;
   Object.keys(this.data).forEach(function (key) {
      self.proxy(key); // 绑定代理属性
   });
   observe(this.data, this);​
   el.innerHTML = this.data[exp]; // 初始化模板数据的值
   new Watcher(this, exp, function (value) {
      el.innerHTML = value;
   });
   return this;
}

Vue.prototype = {
    proxy: function (key) {
        var self = this;
        Object.defineProperty(this, key, {
            enumerable: false,
            configurable: true,
            get: function proxyGetter() {
                return self.data[key];
            },
            set: function proxySetter(newVal) { 
                self.data[key] = newVal;
            }
        });
    }
}

然后就可以通过vue.name,直接改版模板的数据了, 下一步就要实现 解析器Complie

[完]

原文地址:https://www.cnblogs.com/chuchur/p/9396995.html