> 文章列表 > vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的

vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的

vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的

我们上文写的这个方法 并不能很好的侦测对象所有的属性 或者说 不能比较简介的侦测所有属性
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
在实际业务中 对象里面套对象 也不是什么很少见的事 例如这样
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
这种 我们用上一种方法 就很麻烦了 所以 我们需要了解新的方法

要完成完整的属性监听 我们就需要一个工具类 这个类的名字是自己取的 这里建议叫 Observer 字面意思就可以看出 这个类起到一个观察的作用

而这个工具类的作用 就是 将一个object对象中的 每个属性 以及每个层级中的属性 都变成一个侦测的响应式数据

我们打开之前写的项目 将 dataResp.js 代码修改如下

const defineReactive = function(data,key,val) {if(arguments.length == 2){val = data[key];}let subset = observe(val);Object.defineProperty(data,key,{enumerable: true,configurable: true,get() {console.log(`您正在获取${key}的值`);return val},set(value) {console.log(`您正在修改${key}的值,更改后的值为${value}`);if(value == val) {return}val = value;subset = observe(value);}});
}const def = function(obj,key,value,enumerable) {Object.defineProperty(obj,key,{value,enumerable,//true  设为可写writable: true,//true  设为可被删除configurable: true});
}class Observer{constructor(value) {//相当于  给拿到的对象  其中的__ob__绑定 值为thsi,在类中用this 表示取实例本身给__ob__赋值  最后一个enumerable为false 表示属性不参与for遍历def(value,'__ob__',this,false);this.walk(value);}walk(value) {for(let key in value){defineReactive(value,key);}}
}export const observe = function(value) {if(typeof value != "object") return;var ob;if (typeof value.__ob__ !== 'undefined'){ob = value.__ob__ ;}else{ob = new Observer(value);}return ob;
}

被改的非常的复杂 不过不要急 看我效果 我们慢慢讲解
将output.js代码更改如下

import { observe } from "./dataResp"
const output = () => {var obj = {data: {data: {map: {dom: {isgin: true}},arg: 13},name: "小猫猫"},bool: {}};observe(obj);console.log(obj);obj.data.data.map.dom.isgin = false;document.getElementById("text").innerHTML = obj.data.name;
}export default output

我们看看运行效果
首先 是看我们这个obj对象 被console.log输出在控制台 我们发现 他的每一次都被加上了一个 ob 的东西
很多人就会说 啊 我在vue响应式数据中也会看到这个 没错 这就是vue参照的一个实现方式
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
然后我们看运行效果
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
很显然 系统监听到了我们对obj.data.name的访问 所以 连着上面的data也被输出了一下 直到name 这一级结束 这就说明 你操作了一个数据 他的父节点也会被触发 比较 最为一个对象 自己的结构 也是他们内容的一部分 子集被访问或变量 也算是他们被访问或修改

然后很清晰的看到 系统捕获到了我们对obj.data.data.map.dom.isgin的修改

说明 这个方法已经实现了全部内容的监听 那么 我们来讲一下工作原理
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
这个图感兴趣的可以看一下 但老实说 我猜大部分一眼上去是看不懂的 我们从代码上将吧

首先 我们定义了一个 obj 对象 他的内部结构比较复杂 层级比较多 然后引用了dataResp.js 中的observe函数 参数就是我们的obj
然后进到observe中 我们先判断传进来的参数是不是一个对象
如果是对象 就不受影响 继续往下 否则 直接结束语句
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
这里 值得一提的是 因为判断语句是typeof 所以这个判断 数组和对象都是可以进来的 要的也就是这种效果

在这里存一个 ob 变量 用来存返回值

然后我们会判断 当前这个属性下面的 __ob__属性 是不是未定义
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
如果定义了 那么 他就不会等于undefined 就直接让ob等于他的__ob__然后返回就好了 这里 就解密 了 确实 __ob__没什么高大上的 就是一个标记 如果__ob__不是未定义 表示 当前的这个对象已经被处理过了 不用继续了 避免递归死循环
如果没有达到条件 说明 元素 __ob__是未定义状态 就需要处理 我们就new Observer 类 将他传进类里面去
然后我们再来看Observer
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
这里 constructor拿到的这个参数value就是我们new Observer时传进来的对象
然后 我们调用了 def方法 我们来看 def

第一个参数 value 他用在了Object.defineProperty第一个参数 说明 第一个参数是要声明响应式的对象 而这里 我们Observer里传的value 就是我们扔给Observer的对象 而第二个 也作为了Object.defineProperty的第二个参数 要给对象的哪个字段绑定 这里 我们就传了字符串 ob 说明 我们要绑定的是当前对象下的__ob__字段 Object.defineProperty的特性包括 如果对象没有要声明的字段 他会帮你创建 所以 我们没有__ob__他也会帮你创建出来
然后第三个参数 这个字段的值 就是 value对象的__ob__字段的值 我们Observer给了个 this 我们都知道 在类中使用 this 就是拿到当前类的实例 所以 我们将类new出来的实例 赋值给了__ob__
最后一个参数是enumerable 这个我们之前看过 就是 如果这个你设true 你用for遍历这个对象时 就可以看到这个字段 反之 设false 就不会参与for遍历 没人会喜欢这个 __ob__参与遍历吧 里面的信息又没用 他只是个标记
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
然后回到Observer的下半段代码
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
调用了声明在类中的walk方法 传入的参数还是value 当前的实例对象
walk 拿到对象的第一件事就是将他遍历开
而 我们目前这个value是之前传到observe 中的 obj 对象 遍历他 就会遍历出他下面的data和bool
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
然后 for遍历出啦的 对应的是键 而key是键 value 还是obj对象 所以
它遍历两次 传给defineReactive的参数分别是
obj对象, 字符串类型的 “data”
obj对象, 字符串类型的 “bool”

然后 我们来看defineReactive函数
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
进来先判断了 参数是不是只有两个 因为我知道我只传了两个参数啊
所以判断 如果只有两个参数 说明第三个参数 val是没有值的 所以
val = data[key]
根据上面的参数 我们知道 两次遍历调用 这句话分别代表
obj[“data”]
obj[“bool”]
就会取到当前传进来的对象的值
然后继续
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
我们让后面的对象继续去调用最开始obj 调用的 observe
obj在这个过程中给 data和bool声明了响应式 那么 继续回去调用这个方法的就还是data和bool
bool因为是个空的 所以 到Observer 的walk方法中 因为没有子集遍历 而停止
而data会继续带出下面data和name继续参与这个过程
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
data中的data自然没得说 继续重复过程
但 name 因为不是对象或数字 到observe刚开始 就会被判断拦住 并停止
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
继续看会到obj的这一层
vue2数据响应式原理(4) 递归侦测对象所有属性,解密vue响应式对象__ob__是干什么的
因为 这里是通过遍历对象obj获取的key 所以 声明的分别是 obj的两个子集的响应式 第一个参数 要声明响应式的对象 obj 要声明的字段 就是两次遍历出来的key了
最后 在set中的subset = observe(value); 是保证当改变后 继续保存新值的响应