vue 响应式原理

@sakila1012 2018-04-27 05:14:49发表于 sakila1012/blog

image

相信看过 vue 官方文档的同学,对这张图应该相当很熟悉了。

那么 vue 响应式是怎么实现的?

一般都会说数据劫持 + Object.defineProperty
下面是简单版的实现

<div>
  <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>

标准版的如下:

const Observer = function(data) {
  for (var key in data) {
    defineReactive(data, key);
  }
}

const defineReactive = function(obj, key) {
  const dep = new Dep();
  let val = obj[key];
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      console.log('in get');
      dep.depend();
      return val;
    },
    set(newVal) {
       if (newVal === val) {
         return;
       }
       val = newVal;
       dep.notify();
     }
  });
}

const observe = function (data) {
  return new Oserver(data)
}

const Vue = function (options) {
  const self = this;
  if (options && typeof options.data === 'function') {
    this._data = options.data.apply(this);
  }

  this.mount = function () {
    new Watcher(self, self.render);
  }

  this.render = function() {
    with(self) {
      _data.text;
    }
  }
  observer(this._data);
}

const Watcher = function(vm, fn) {
  const self = this;
  this.vm = vm;
  Dep.target = this;

  this.addDep = function(dep) {
    console.log('in watcher update');
    fn();
  }
  this.value = fn();
  Dep.target = null;
}

const Dep = function() {
  const self = this;
  this.target = null;
  this.subs = [];
  this.depend = function() {
    if (Dep.target) {
      Dep.target.addDep(self);
    }
  }

  this.addSub = function(watcher) {
    self.subs.push(watcher);
  }
  
  this.notify = function() {
    for (var i=0; i<self.subs.length; i += 1) {
      self.subs[i].update();
    }
  }
}

const vue = new Vue({
  data() {
    return {
      text: 'hello world'
    }
  }
})

vue.mount();
vue._data.text = '123';