赞
踩
在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中的数据响应式:
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函数的原型链上
- }
- });
- });
- }
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();
- });
- };
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 的属性值。
首先,我们需要创建一个 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。