美文网首页
vue 响应式原理

vue 响应式原理

作者: 田帅奇 | 来源:发表于2018-09-06 12:12 被阅读0次

借助es5的Object.defineProperty实现vue数据劫持原理
首先画一个html页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script src="./watcher.js"></script>
  <script src="./observer.js"></script>
  <script src="./mvvm.js"></script>
  <script>
    const vm = new MVVM({
      el: '#app',
      data: {
        country: 'china',
        content: 'family',
        family: {
          count: 4
        }
      },
      computed: {
        ageTotal() {
          return this.family.father.age + this.family.mother.age + this.family.children.age
        }
      },
      watch: {
        "family.count": function(nV, oV){
          console.log(nV);
        }
      }
    });
    vm.family.count = 100;
  </script>
</body>
</html>

看一下mvvm.js 的内容

function MVVM (options){
  this.$options = options;
  var data = this._data = this.$options.data;
  let me = this;
  // 将this.aaa代理到this._data.aaa
  Object.keys(data).forEach(key => {
    me._proxyData(key);
  });
  // 初始化计算属性
  this._initComputed();
  // 利用递归进行数据劫持
  observe(data, this);
  // 初始化watcher
  this._initWatcher();
}
MVVM.prototype = {
  _proxyData: function(key ,setter, getter){
    let me = this;
    setter = setter || 
    Object.defineProperty(me, key, {
      configurable: false,
      enumerable: true,
      get: function proxyGetter(){
        return me._data[key];
      },
      set: function proxySetter(newVal){
        me._data[key] = newVal;
      }
    });
  },
  _initComputed: function () {
    let me = this;
    let computed = this.$options.computed;
    if (typeof computed === 'object') {
      Object.keys(computed).forEach(key => {
        Object.defineProperty(me, key, {
          get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
          set: function (){}
        });
      });
    }
  },
  _initWatcher: function () {
    let me = this;
    let watcher = this.$options.watch;
    if (typeof watcher === 'object') {
      Object.keys(watcher).forEach(key => {
        new Watcher(me, key, watcher[key]);
      });
    }
  }
}

看一下observer的内容

function Observer(data){
  this.data = data;
  this.walk(data);
}
Observer.prototype = {
  walk: function(data){
    let me = this;
    Object.keys(data).forEach(key => {
      me.convert(key, data[key]);
    });
  },
  convert: function (key, val) {
    this.defineReactive(this.data, key, val);
  },
  defineReactive: function (data, key, val) {
    let dep = new Dep();
    let childObj = observe(val);
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: false,
      get: function() {
        // 使用watch aaa.bbb时候,会触发两次get,且两次实例的dep是不同的,watch队列会被push进两个dep,如果继续使用this.add.bbb也会持续触发get,但是wather队列不会继续添加该dep
        if (Dep.target) {
          dep.depend();
        }
        return val;
      },
      set: function(newVal) {
        if (newVal === val) {
          return;
        }
        val = newVal;
        childObj = observe(newVal);
        dep.notify();
      }
    });
  }
};

function observe(value, vm){
  if (!value || typeof value !== 'object') {
    return
  }
  return new Observer(value);
}

var uid = 0;
Dep.target = null;

function Dep () {
  this.id = uid++;
  this.subs = [];
};
Dep.prototype = {
  depend: function () {
    Dep.target.addDep(this);
  },
  addSub: function (sub) {
    // 当使用watch a.b时候getter会触发两次,watcher会被push到当前sub中,sub是当前的watcher
    this.subs.push(sub);
  },
  notify: function(){
    this.subs.forEach(sub => {
      sub.update();
    });
  }
};

看一下watcher的内容

function Watcher(vm, expOrFn, cb){
  this.cb = cb;
  this.vm = vm;
  this.expOrFn = expOrFn;
  this.depIds = {};
  if (typeof expOrFn === 'function') {
    this.getter = expOrFn;
  } else {
    this.getter = this.parseGetter(expOrFn);
  }
  this.value = this.get();
};

Watcher.prototype = {
  parseGetter: function(exp) {
    if (/[^\w.$]/.test(exp)) return; 
    var exps = exp.split('.');
    return function(obj) {
      for (var i = 0, len = exps.length; i < len; i++) {
          if (!obj) return;
          obj = obj[exps[i]];
      }
      return obj;
    }
  },
  get: function () {
    Dep.target = this;
    let value = this.getter.call(this.vm, this.vm);
    Dep.target = null
    return value;;
  },
  addDep: function (dep) {
    if (!this.depIds.hasOwnProperty(dep.id)) {
      dep.addSub(this); // 当使用watch aaa.bbb时候,会触发两次get,因此该函数会执行两次,dep.id是不同的,然后不同的dep会执行addSub,把当前的watcher放到当前dep的subs队列中
      this.depIds[dep.id] = dep;
    }
  },
  update: function () {
    this.run();
  },
  run: function () {
    let value = this.get();
    let oldVal = this.value;
    if (value !== oldVal) {
      this.value = value;
      this.cb.call(this.vm, value, oldVal);
    }
  }
};

大功告成!!!

相关文章

网友评论

      本文标题:vue 响应式原理

      本文链接:https://www.haomeiwen.com/subject/nxxxgftx.html