浅析Vue2.0的响应式

314 阅读1分钟

Object类型

Vue2.0的响应式原理是Object.defineProperty(obj, key, {}),在getter中收集依赖,在setter中触发依赖;还有一个是观察者模式。

Observer

原始数据通 data 通过 Observer 遍历自己的 key,通过get、set函数为每个属性添加侦测。

class Observer {
  constructor(value) {
    this.value = value;

    if(!Array.isArray(value)){
      this.walk(value);
    }
  }

  //walk会将每一个属性都转换成getter/setter的形式来侦测变化,这个方法只有在数据类型为Object时被调用
  walk(obj){
    for(let key in obj){
      defineReactive(obj, key, obj[key]);
    }
  }
}

function defineReactive(data, key, val) {
  //递归属性
  if(typeof val==='object'){
    new Observer(val)
  }
  let dep = new Dep();
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    get(){
    //如果有用到这个key的值, 进行依赖收集
      if(window.target){
        dep.depend();
      }
      return val;
    },
    set(newVal){
      if(val===newVal){
        return;
      }
      val = newVal;
      //如果key的值发生了变化, 通知依赖,触发响应的更新
      dep.notify();
    }
  })
}

Dep

数据data每个属性key都有一个数组用来存放哪些地方用到了这个数据的这个属性。统一放在一个Dep实例中。Dep一方面用来存储这些依赖,另一方面属性key的值变化了(set)的时候去通知这些依赖,触发相应的更新(update)。

class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub){
    this.subs.push(sub);
  }

  depend(){
    if(window.target){
      this.subs.push(window.target);
    }
  }

  notify(){
    const subs = this.subs.slice();
    for(let sub of subs){
      sub.update();
    }
  }
}

Watcher

依赖搜集。

  • 触发key值的get方法,将自己添加到对应key的依赖数组中。(这里有个技巧是先缓存到window.target上,触发完成get方法后再将window.target置为undefined)
  • 要有update方法,当key的值变化时触发相应的update。
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    //执行this.getter(), 就可以读取data.a.b.c的内容
    this.getter = parsePath(expOrFn);
    this.cb = cb;
    this.value = this.get();
  }
  get(){
    window.target = this;
    let value = this.getter.call(this.vm, this.vm);
    window.target = undefined;
    return value;
  }

  update(){
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue)
  }
}

const bailRE = /[^\w.$]/;
function parsePath(path) {
 if(bailRE.test(path)){
   return;
 }
 const segments = path.split('.');
 return function (obj) {
  for(let i=0; i<segments.length;i++){
    if(!obj) return;
    obj = obj[segments[i]]
  }
  return obj;
 }
}

验证

<input type="text" id="myInput">
<p></p>
</body>
<script>
    let vm = {
      data: {
        message: 'hello'
      }
    };

    document.getElementById("myInput").value = vm.data.message;
    document.querySelector('input').oninput = function(v, a){
      let x = document.getElementById("myInput").value;
      vm.data.message = x;
    }
    document.querySelector('p').innerHTML = vm.data.message;

    new Observer(vm);
    new Watcher(vm, "data.message", function (newVal, val) {
      document.querySelector('p').innerHTML = newVal;
    });

</script>

参考:www.cnblogs.com/chenhuichao…
代码来自《深入浅出Vue.js》