在Vue中,数据响应式Observe是通过使用闭包来实现的。简单来说,就是利用了JavaScript中函数作用域链的特性,将数据对象以及相关的数据操作函数保存在闭包内部,从而达到对数据的监听、更新等操作。
Vue 数据响应式是指当 Vue 实例的数据发生变化时,页面中相应的内容也会随之更新,这种机制的实现依赖于 Observe、Dep 和 Watcher 这三个核心概念。
Observe
Observe 负责将一个普通的对象转换成响应式对象。Vue 通过使用 Object.defineProperty() 方法,将对象的属性转换成 getter 和 setter,当数据发生变化时,会自动触发相应的更新函数,实现数据的响应式。
Dep
Dep(Dependency)用来管理 Watcher,每个响应式对象都会有一个对应的 Dep 对象,Dep 对象里面存放着所有观察该响应式对象的 Watcher。当响应式对象的数据发生变化时,它会通过 Dep 通知所有观察该响应式对象的 Watcher,告诉它们需要重新计算。
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