Vue:object变化侦测

变化侦测指的是对数据进行监听当数据发生变化时,对变化的数据重新渲染,实现响应式渲染。在Vue中object和array的变化侦测是不一样的,这一篇主要介绍object的变化侦测。

object的变化侦测的核心是监听object数据的变化,可以通过Object.defineProperty和Proxy实现,Vue2.x时,ES6支持不是很理想,采用的是Obejct.definedProperty,Vue3已经使用Proxy对数据侦测进行重写。这一篇主要以Vue2.x为例介绍其原理,虽然Vue3使用的是Proxy,但是原理都一样。

object变化侦测的流程是当对象触发getter时对依赖进行收集,当对象触发setter时触发与该数据相关的所有依赖,就是通知所有使用了该数据的地方。总结为一句话就是:getter收集依赖,setter触发依赖。
在这里插入图片描述

既然知道了是使用Object.defineProperty可以侦听到对象的变化,那么就封装一个用于侦听对象变化的函数。

function defineReactive(data, key, val) {
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    set(newVal) {
      if (val === newVal) {
        return;
      }
      val = newVal;
    },
    get() {
      return val;
    }
  });
}

不过只是侦测对象的变化还不够,还得需要在数据变化时,做一些其他的处理,才能让数据动态显示。接下来对该函数做一些改造。

function defineReactive(data, key, val) {
	let dep = [];
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    set(newVal) {
      if (val === newVal) {
        return;
      }
       for(let i = 0; i < dep.length; i++){
		dep[i](newVal, val);		
	  }
      val = newVal;
    },
    get() {
      dep.push(function(){});
      return val;
    }
  });
}

这样就可以在数变化的时候在dep中的函数执行自定义逻辑。接下来,进行一些优化,把自定义的逻辑回调函数,分离出来,单独进行管理。
这个Dep类专门用来管理回调函数。

class Dep {
  constructor() {
    // 用来存储回调函数
    this.subs = [];
  }
  // 添加回调函数辅助函数
  addSub(sub) {
    this.subs.push(sub);
  }
  // 追加回调函数
  dependSub() {
    // 为undefined时不再添加,否则会一直添加导致死循环
    if (window.target) {
    // 添加固定的属性,值可以改变
      this.addSub(window.target);
    }
  }
  // 移除回调函数
  removeSub(sub) {
    this.remove(this.subs, sub);
  }
  // 执行回调函数
  notify() {
    let subs = this.subs.slice(0);
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();
    }
  }
}
// 移除回调函数辅助函数
function remove(subs, sub) {
  for (let i = 0; i < subs.length; i++) {
    if (sub === subs[i]) {
      subs.splice(i, 1);
      return;
    }
  }
}

然后对defineReactive函数进行改造

function defineReactive(data, key, val) {
  let dep = new Dep();
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    set(newVal) {
      if (val === newVal) {
        return;
      }
      val = newVal;
      dep.notify();
    },
    get() {
      dep.dependSub();
      return val;
    }
  });
}

接下来对需要收集的依赖进行管理。依赖就是Watcher。Watcher先存储在全局指定的位置,然后读取数据,触发getter操作收集依赖,哪个Watcher触发getter就把哪个Watcher存储在Dep中,在触发getter时,将所有的Watcher都通知一遍。

class Watcher {
  constructor(vm, expOrFn, cb) {
    // vm实例
    this.vm = vm;
    // 存储用于触发对象getter操作的函数,当执行getter时会访问侦测对象中的属性,以触发对象的getter操作,收集依赖
    this.getter = parsePath(expOrFn);
    // 存储回调函数
    this.cb = cb;
    // 存储属性的当前值
    this.value = this.get();
  }
  // 通过访问对象属性,触发getter操作,开始收集依赖
  get() {
    window.target = this;
    let value = this.getter.call(this.vm, this.vm);
    // 手动设置为undefined,用于是否继续添加依赖的判断
    window.target = undefined;
    return value;
  }
  // 当侦测到对象数据变化会执行该函数
  update() {
    let oldValue = this.value;
    // 存储对象的新值,同时重新收集依赖
    this.value = this.get();
    // 执行回调函数
    this.cb.call(this.vm, this.value, oldValue);
  }
}
// 解析变量简单访问路径
function parsePath(path) {
  let segments = path.split('.');
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return;
      obj = obj[segments[i]];
    }
    return obj;
  }
}

接下来再对对象侦测进行封装。

class Observer {
  constructor(value) {
    if (!Array.isArray(value)) {
      this.walk(value);
    }
  }
  walk(data) {
    let keys = Object.keys(data);
    // 监听对象的所有属性
    for (let i = 0; i < keys.length; i++) {
      defineReactive(data, keys[i], data[keys[i]]);
    }
  }
}
function defineReactive(data, key, val) {
// 递归监听对象的子属性
  if (typeof val === 'object') {
    new Observer(val);
  }
  let dep = new Dep();
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    set(newVal) {
      if (val === newVal) {
        return;
      }
      val = newVal;
      dep.notify();
      console.log('执行依赖');
    },
    get() {
      dep.dependSub();
      return val;
    }
  });
}

后面侦测对象时只需要new 一下即可

let obj = {}
new Observer(obj);

最后可以进行测试,当数据变化时是否能够触发回调函数。

let vm = {
  data: {
    addr: 'hubeiwuahn'
  },
  $watcher: function (expOrFn, cb) {
    new Watcher(this, "data." + expOrFn, cb);
  }
}
new Observer(vm.data);
vm.$watcher('addr', function (newValue, oldValue) {
  console.log('监听开始', newValue, oldValue);
});
vm.data.addr = 'beijing';

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端御书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值