当前位置:   article > 正文

Vue数据响应式原理:Observe、Dep、Watcher_dep 与watcher

dep 与watcher

在Vue中,数据响应式Observe是通过使用闭包来实现的。简单来说,就是利用了JavaScript中函数作用域链的特性,将数据对象以及相关的数据操作函数保存在闭包内部,从而达到对数据的监听、更新等操作。

Vue 数据响应式是指当 Vue 实例的数据发生变化时,页面中相应的内容也会随之更新,这种机制的实现依赖于 Observe、Dep 和 Watcher 这三个核心概念。

  1. Observe

Observe 负责将一个普通的对象转换成响应式对象。Vue 通过使用 Object.defineProperty() 方法,将对象的属性转换成 getter 和 setter,当数据发生变化时,会自动触发相应的更新函数,实现数据的响应式。

  1. Dep

Dep(Dependency)用来管理 Watcher,每个响应式对象都会有一个对应的 Dep 对象,Dep 对象里面存放着所有观察该响应式对象的 Watcher。当响应式对象的数据发生变化时,它会通过 Dep 通知所有观察该响应式对象的 Watcher,告诉它们需要重新计算。

  1. Watcher

Watcher 用来观察数据的变化,当数据发生变化时,它会通知订阅该数据的组件更新视图。Watcher 在实例化时会将自己添加到 Dep 中,当响应式对象的数据发生变化时,会触发相应的更新函数,通知所有观察该响应式对象的 Watcher 执行更新操作。

下面是一个简单的例子,演示了如何使用闭包来实现Vue中的数据响应式:

1、observe(data) 函数的实现

observe 函数的作用是利用 Object.defineProperty 函数来实现数据响应式,其主要实现步骤如下:

  • 利用闭包保存数据对象 data;

  • 遍历数据对象的属性,利用闭包保存属性的值,并创建属性对应的依赖列表;

  • 利用 Object.defineProperty 函数重新定义属性的 getter 和 setter 方法,以实现数据响应式。

  1. function observe(data) {
  2. // 利用闭包保存数据对象
  3. let obj=data;
  4. // 遍历数据对象的属性
  5. Object.keys(obj).forEach(key=> {
  6. // 利用闭包保存属性的值,保存在val中
  7. let val=obj[key];
  8. // 创建属性对应的依赖列表,保存在的dep中,dep为由构造函数构造的对象
  9. let dep=newDep(); //this.deps 数组来保存依赖列表;
  10. // 利用Object.defineProperty()方法设置属性的getter和setter方法
  11. Object.defineProperty(obj, key, {
  12. // 可枚举 // 可配置
  13. enumerable: true, configurable: true,
  14. // 当获取该属性值时触发的函数
  15. get: function() {
  16. // 将当前依赖添加到依赖列表dep中
  17. if (Dep.target) {
  18. dep.addDep(Dep.target); //该方法位于Dep函数的原型链上
  19. }
  20. returnval;
  21. },
  22. // 当设置该属性值时触发的函数
  23. set: function(newVal) {
  24. if (newVal===val) {
  25. return;
  26. }
  27. val=newVal;
  28. // 通知依赖更新
  29. dep.notify(); //该方法位于Dep函数的原型链上
  30. }
  31. });
  32. });
  33. }
2、Dep() 函数的实现

Dep() 函数是用来创建依赖列表的,其主要实现步骤如下:

  • 利用 this.deps 数组来保存依赖列表;

  • 定义 addDep(dep) 方法,用于添加依赖;

  • 定义 notify() 方法,用于通知依赖更新。

  1. //Dep() 函数
  2. // 依赖列表
  3. function Dep() {
  4. this.deps= [];
  5. }
  6. // 用于保存当前的watcher对象
  7. Dep.target=null;
  8. // 添加依赖到列表中
  9. Dep.prototype.addDep=function(dep) {
  10. this.deps.push(dep);
  11. };
  12. // 通知依赖列表更新
  13. Dep.prototype.notify=function() {
  14. this.deps.forEach(dep=> {
  15. dep.update();
  16. });
  17. };
3、Watcher() 函数的实现

Watcher() 函数是用来监听数据的变化并触发相应的回调函数的,其主要实现步骤如下:

  • 利用闭包保存 Watcher 对象;

  • 定义 get() 方法,用于获取属性值;

  • 定义 update() 方法,用于更新视图。

在实例化 Watcher 对象的过程中,我们会将 Dep.target 设置为当前 Watcher 对象,以便在属性的 getter 方法中添加当前 Watcher 对象到依赖列表中。

  1. // Watcher类用于监视数据的变化,其中利用闭包保存watcher对象
  2. function Watcher(vm, exp, cb) {
  3. this.vm=vm;
  4. this.exp=exp;
  5. this.cb=cb;
  6. // 将当前watcher对象保存到Dep.target中,以便后面访问属性值时可以添加依赖
  7. Dep.target=this;
  8. this.value=this.get();
  9. // 清空Dep.target,以免影响其他watcher对象的依赖添加
  10. Dep.target=null;
  11. }
  12. // 获取属性值,如果属性是多级的,则按照路径依次访问属性值
  13. Watcher.prototype.get=function() {
  14. letvalue=this.vm;
  15. // 按照属性路径依次访问属性值
  16. this.exp.split('.').forEach(key=> {
  17. value=value[key];
  18. });
  19. return value;
  20. };
  21. // 更新watcher对象的回调函数,用于更新视图。
  22. Watcher.prototype.update=function() {
  23. let value=this.get();
  24. let oldValue=this.value;
  25. if (value!==oldValue) {
  26. this.value=value;
  27. this.cb.call(this.vm, value, oldValue);
  28. }
  29. };
  30. Watcher.prototype.get

这个 get 方法首先获取了 Vue 实例对象,即 this.vm。接着,它按照属性路径依次访问属性值。this.exp 是 Watcher 对象的属性路径,可能包含多个属性名,以点号分隔。通过将属性路径按照点号分隔成数组,可以依次访问每个属性的值。在每次循环中,根据当前的属性名,使用 value[key] 的方式访问到 value 对象的下一级属性值。最后返回获取到的属性值。

例如,如果属性路径为 person.name,那么首先会获取 Vue 实例对象 this.vm,然后将属性路径按照点号分隔成数组 ['person', 'name']。接下来循环这个数组,第一次循环时,key 的值为 'person',那么 value = value[key] 的结果就是获取 this.vm 对象的 person 属性值。接着进入下一次循环,此时 key 的值为 'name',那么 value = value[key] 的结果就是获取 person 对象的 name 属性值。最终返回的就是 person.name 的属性值。

4、创建 Vue 实例并监听数据

首先,我们需要创建一个 Vue 实例,并将其传入 observe(data) 函数中,以实现数据响应式。

然后,我们实例化一个 Watcher 对象,并传入需要监听的属性名,以及相应的回调函数。

最后,我们修改数据,并观察控制台输出的结果。

模拟一下运行过程

假设数据为:

  1. let data= {
  2. name: 'John',
  3. age: 25,
  4. address: {
  5. city: 'New York',
  6. country: 'USA'
  7. }
  8. }

运行过程如下:

1、创建Vue实例

  1. let vm=newVue({
  2. data: {
  3. name: 'John',
  4. age: 25,
  5. address: {
  6. city: 'New York',
  7. country: 'USA'
  8. }
  9. }
  10. });

2、监听数据

observe(vm);

在observe函数中,遍历data对象的属性,为每个属性创建一个Dep对象,利用Object.defineProperty()方法设置属性的getter和setter方法。当访问属性值时,如果Dep.target存在,则将其添加到对应属性的依赖列表中。当设置属性值时,如果新旧值不相等,则更新属性值并通知其对应的依赖列表。

3、创建Watcher实例

  1. new Watcher(vm, 'address.city', function(value, oldValue) {
  2. console.log(`Value changed from ${oldValue}to ${value}`);
  3. });

创建一个Watcher实例,将其保存到Dep.target中,并调用Watcher.prototype.get()方法获取属性值,并将Watcher实例添加到该属性的依赖列表中。当属性值发生变化时,Watcher实例的update()方法会被调用,比较新旧值是否相等,如果不相等则调用Watcher实例的回调函数,输出变化的信息。

4、修改数据

vm.address.city='San Francisco';

设置address.city属性的值为'San Francisco'。这会触发该属性的setter方法,更新属性值并通知其对应的依赖列表。依赖列表中保存着Watcher实例,因此Watcher实例的update()方法会被调用,比较新旧值是否相等,如果不相等则调用Watcher实例的回调函数,输出变化的信息。

输出结果为:Value changed from New York to San Francisco

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/248240
推荐阅读
相关标签
  

闽ICP备14008679号