在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 方法,以实现数据响应式。

function observe(data) {
  // 利用闭包保存数据对象
  let obj=data;
  // 遍历数据对象的属性
  Object.keys(obj).forEach(key=> {
    // 利用闭包保存属性的值,保存在val中
    let val=obj[key];
    // 创建属性对应的依赖列表,保存在的dep中,dep为由构造函数构造的对象
    let dep=newDep();                 //this.deps 数组来保存依赖列表;
    // 利用Object.defineProperty()方法设置属性的getter和setter方法
    Object.defineProperty(obj, key, {
      // 可枚举                     // 可配置
      enumerable: true,            configurable: true,
      // 当获取该属性值时触发的函数
      get: function() {
        // 将当前依赖添加到依赖列表dep中
        if (Dep.target) {
          dep.addDep(Dep.target);         //该方法位于Dep函数的原型链上
        }
        returnval;
      },
      // 当设置该属性值时触发的函数
      set: function(newVal) {
        if (newVal===val) {
          return;
        }
        val=newVal;
        // 通知依赖更新
        dep.notify();                      //该方法位于Dep函数的原型链上
      }
    });
  });
}
2、Dep() 函数的实现

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

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

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

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

//Dep() 函数
// 依赖列表
function Dep() {
  this.deps= [];
}
// 用于保存当前的watcher对象
Dep.target=null;
// 添加依赖到列表中
Dep.prototype.addDep=function(dep) {
  this.deps.push(dep);
};
// 通知依赖列表更新
Dep.prototype.notify=function() {
  this.deps.forEach(dep=> {
    dep.update();
  });
};
3、Watcher() 函数的实现

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

  • 利用闭包保存 Watcher 对象;

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

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

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

// Watcher类用于监视数据的变化,其中利用闭包保存watcher对象
function Watcher(vm, exp, cb) {
  this.vm=vm;
  this.exp=exp;
  this.cb=cb;
  // 将当前watcher对象保存到Dep.target中,以便后面访问属性值时可以添加依赖
  Dep.target=this;
  this.value=this.get();
  // 清空Dep.target,以免影响其他watcher对象的依赖添加
  Dep.target=null;
}
// 获取属性值,如果属性是多级的,则按照路径依次访问属性值
Watcher.prototype.get=function() {
  letvalue=this.vm;
  // 按照属性路径依次访问属性值
  this.exp.split('.').forEach(key=> {
    value=value[key];
  });
  return value;
};
// 更新watcher对象的回调函数,用于更新视图。
Watcher.prototype.update=function() {
  let value=this.get();
  let oldValue=this.value;
  if (value!==oldValue) {
    this.value=value;
    this.cb.call(this.vm, value, oldValue);
  }
};
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 对象,并传入需要监听的属性名,以及相应的回调函数。

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

模拟一下运行过程

假设数据为:

let data= {
  name: 'John',
  age: 25,
  address: {
    city: 'New York',
    country: 'USA'
  }
}

运行过程如下:

1、创建Vue实例

let vm=newVue({
  data: {
    name: 'John',
    age: 25,
    address: {
      city: 'New York',
      country: 'USA'
    }
  }
});

2、监听数据

observe(vm);

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

3、创建Watcher实例

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

创建一个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

Logo

前往低代码交流专区

更多推荐