Vue中数据响应式原理——假递归监测对象类型的所有属性
此篇是再 Vue响应式原理解析(一) 的基础之上展开
文章目录
情景准备
我们实验可以发现,单层 的obj对象里的所有属性是都可以被 defineReactive 方法通过 Object.defineProperty 监测到的,但是多层嵌套的情况下则内层无法被监测到,所以我们首先想到的就是通过递归让obj的每一层被监测到。
function defineReactive(data , key , value){
if(arguments.length == 2){
value = data[key]
}
Object.defineProperty(data,key,{
enumerable: true, // 可以被枚举
configurable: true, // 可以被操作 例如delete
get(){
console.log(`正在访问${key}`)
return value
},
set(newValue){
if(value == newValue) return
console.log(`正在改变${key}`)
value = newValue
}
})
}
let obj = {
a:10,
m:{
n:20
}
}
defineReactive(obj , 'n') // 这个写了也没用,不论你写 m.n还n 都是是都相当于重新创建了个属性,而无法对我们想要的属性进行监测
defineReactive(obj , 'a')
console.log(obj.m.n)
console.log(obj.a)
很明显,单层的a可以被监测到(因为输出了正在访问a),而obj.m.n则不可以(没有输出),所以要进行循环,解析出每一层实现监测。
开始思路(思路很重要,一定要先理解思路再去看代码)
既然提到了循环,并且我们也知道了可以通过 defineReactive 方法调用 Object.defineProperty 来实现监测,所以自然而然想到以下代码。
for(let key in value){
defineReactive(value , key)
}
而 for-in
方法并不可以把对象的所有内层遍历出来,所有我们想到的操作就是在 defineReactive
方法中再次把上次传进来的一层再次进行for-in
,依次实现循环,直到传入的值不再是对象类型为止,核心我们已经找到。
参考Vue的思路来写…
按照我理解的思路来写:
- 创建了一个
Observer
类,一方面进行for-in
中转,另一方面是进行优化,使用单例模式提高性能。 - 既然提到了单例模式,那么就得有一个单例模式的标识,参考Vue代码可以发现是通过
__ob__
这个属性来实现的,所以我们可以再写个方法叫def
来给已经可以被监测的层级添加__ob__
。 - 所以写了一个
observe
方法来进行单例模式(实例化) - 所以总体思路就清晰了,首先我们通过
observe
进行判断是否已经开启监测,如果没有开启,则进行实例化通过Observer
for-in --> defineReactive
对当前层级进行检测,然后defineReactive
再把最新一层给observe
方法,查看是否已经添加监测,直到最新一层不是对象的时候终止,依以此达到一个闭环,实现对象的所有层级实现被监测(响应式)。
思路大概就是这样,下面就不过多赘述了,直接上代码,大伙儿可以参考代码看看我的思路,方便理解记忆。
代码
Observer类(为单例模式做准备,并且进行for-in)
def
方法,创建__ob__
标识,并且要让他为不可被枚举
的,拆分出去会让代码更加明了(不喜欢也可以直接写到Observer
中)。
// 为__ob__服务,因为我们不想让__ob__成为可枚举的,所以要通过他去创建__ob__
export default function def(obj , key , value , enumerable) {
Object.defineProperty(obj,key,{
value,
enumerable,
configurable: true,
writable: true
})
}
Observer
这里的value
包括以后的value
其实都是for-in
遍历过后的新的一层对象
import def from './def.js'
import defineReactive from './defineReactive.js'
// observer 将一个正常的obj转换成一个每个层级的属性都是响应式的obj
export default class Observer {
constructor(value) {
// 这里的this代表的是Observer的实例也就是新的一层的对象(看observe),而他的实例本身则是一层一层的对象,其实就是为了确保不重复去创建Observe实例(单例模式)
def(value , '__ob__' , this ,false)
console.log('我是Observer类',value)
this.walk(value)
}
walk(value){
for(let key in value){
defineReactive(value , key)
}
}
}
observe方法(使用单例模式,它也是所有方法的入口所在)
import Observer from './Observer.js'
// Observer 将一个正常的obj转换成一个每个层级的属性都是响应式的obj
// observe 函数是用来监测obj的每一层是否是对象
export default function observe (value){
if(typeof value != 'object') return;
// ob为Observer的实例
let ob
if(typeof value.__ob__ !== 'undefined'){
ob = value.__ob__
}else{
// 也就是在Observer里添加了__ob__
ob = new Observer(value)
}
return ob
}
defineReactive方法(也是主要工作的核心所在,使用了Object.defineProperty)
在Observer
类中的for--in
里我们是只传了遍历出来的一层的上一层的对象和属性名,所以需要我们去组合成下一层的vaule。
注意:
for-in
里的value是代表上一层对象,而这里的value
则代表下一层需要添加监测的对象,这里刚开始会比较容易弄混。
import observe from './observe.js'
/**
*
* @param {传进来的对象} data
* @param {对象的属性名} key
* @param {对象的属性值} value
*/
export default function defineReactive(data , key , value){
if(arguments.length == 2){
value = data[key]
}
// 将新的属性值再次进行observe,对他的属性进行监测,就是一层一层给检测到,直到传入的不是对象结束
observe(value)
Object.defineProperty(data,key,{
enumerable: true, // 可以被枚举
configurable: true, // 可以被操作 例如delete
get(){
console.log(`正在访问${key}`)
return value
},
set(newValue){
if(value == newValue) return
console.log(`正在改变${key}`)
value = newValue
}
})
}
效果
至此,我们想要的闭环实现了,直到遍历的最里层不再是对象为止,但是到目前为止,我们也仅仅是实现了对
Object
类型的数据进行监测(响应式),Array
类型的数据还做不到监测(响应式),下一篇将是Array
类型实现响应式。如有错误支持欢迎指出,互相交流学习。