我们已经实现了 Vue
接收存储参数并将 $data
中的属性转换成 getter/setter
,注入到 vue
实例中。
在上篇中我们也提到过 $data
中的 setter
是真正监视数据变化的地方 ,所以还需要将 $data
中的数据转换成 getter/setter
,当触发其 set
方法修改数据时发出通知。
创建 Observer
类
所以我们要创建 Observer
类,在其中实现以下功能:
- 将
$data
中的属性转换成响应式对象 $data
中的属性如果是对象,则该对象的属性也要转换为响应式对象$data
中的数据发生变化时发送通知
我们模拟 vue
中的写法,定义 walk
和 defineReactive
方法,在 walk
中循环调用 defineReactive
方法,在 defineReactive
方法中使用 Object.defineProperty
将数据转换为 getter/setter
。
js
class Observer {
constructor (data) {
this.walk(data)
}
walk (data) {
// 判断data是否是对象
if (!data || typeof data !== 'object') return
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get () {
return val
},
set (newValue) {
if (newValue === val) return
val = newValue
// 发送通知
}
})
}
}
使用 Observer 类
在 vue
类中创建 observer
实例
js
this._proxyData(this.$data)
+ new Observer(this.$data)
在 html
中引入 observer.js
文件
通过以上两步就引入了 observer
,此时在控制台上打印 vm
,就可以看到 $data
中的属性也已经转换成了 getter/setter
。
defineReactive 的第三个参数
js
Object.keys(data).forEach(key => {
// 有 data 和 key,为什么还要传递 data[key] ?
this.defineReactive(data, key, data[key])
})
在调用 defineReactive
方法的时候,有 data
和 key
,为什么还要传递 data[key]
,是因为我们在使用 Object.defineProperty
的 get
方法的时候需要用到 val
,我们试着将这个 val
改成 obj[key]
试试:
js
get () {
// return val
return obj[key]
},
然后在 html
中打印 vm.msg
的值,在控制台上我们可以看到如下的错误: 这个错误说明在我们获取 vm.msg
的时候发生了死循环,造成了堆栈溢出。
当我们获取 vm.msg
的时候首先执行了 vm.msg
的 get
方法,此方法中返回的是 $data.msg
,所以我们又触发了 $data.msg
的 get
事件,如果返回 obj[key]
(就是 $data.msg
),那就会循环触发 $data.msg
的 get
事件。
其实重点在于在 Object.defineProperty
中定义 get
方法前从 obj[key]
中获取到值,而不是每次 get
的时候再去获取,也不一定需要传参过来,只要在此之前获取即可。
由于 Object.defineProperty
中使用的 obj
就是 $data
,这是定义在 vm.$data
上的值,他不会销毁,所以形成了闭包 ,保证 get
方法中的 val
一直存在于闭包中。
嵌套对象转换为响应式
当前 data
中的 msg
和 count
都是原始值,如果 data
中存在对象,那么对象中的属性暂时还不是响应式的,我们需要将嵌套对象中的属性也改变为响应式的。
js
defineReactive (obj, key, val) {
// 如果val是对象,把val内部的属性转换成响应式数据
+ this.walk(val)
Object.defineProperty...
}
这样可以保证初始化的时候如果属性为对象,那么循环调用 walk
方法将内部属性都转换成 getter/setter
。但是当我们重新给 data
中的值赋值为对象的时候,这里的对象就不是响应式的了,所以我们还需要在修改值的时候将对象属性的内部也转换为响应式的。
js
defineReactive (obj, key, val) {
+ let that = this
this.walk(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get () {
return val
},
set (newValue) {
if (newValue === val) return
val = newValue
+ that.walk(newValue)
// 发送通知
}
})
}