vue数据双向绑定原理-watcher

邱秋 • 2017年10月17日 • 阅读:3774 • vue javascript

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

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

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

  • 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

[完]

我,秦始皇,打钱!