数据要在获取时收集依赖,在设置时触发更新依赖,核心方法defineReactive
function defineReactive(data, key, value) {
// 存放依赖的数组
let dep = []
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖,这个依赖就是挂载在全局上的方法
dep.push(window.target)
return value
},
set(newValue) {
if (val === newValue) return
// 赋值时,更新所有的依赖
for (let i = 0; i < dep.length; i++) {
dep[i](newValue, val)
}
val = newValue
}
})
}
封装的收集依赖的辅助方法
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
remove(this.subs, sub)
}
depend() {
if (window.target) {
this.addSub(window.target)
}
}
notify() {
const subs = this.subs.slice()
for (let i = 0; i < subs.length; i++) {
subs[i].update()
}
}
}
function remove(arr, item) {
if (arr.length) {
let index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
改写响应式数据劫持的方法
function defineReactive(data, key, value) {
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
dep.depend()
return value
},
set(newValue) {
if (val === newValue) return
val = newValue
dep.notify()
}
})
}
依赖谁谁?就是上面的 window.target
,那么他到底是啥?其实就是要通知用到这个状态的地方,
它有可能是个模块,也有能是一个 watch
,那么我们就需要封装这样一个集中处理不同依赖类型的方法,
我们先通知这个方法,然后这个方法内部再去通知其他地方。Watcher
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm
this.getter = parsePath(expOrFn)
this.cb = cb
this.value = this.get()
}
get() {
window.target = this
let value = this.getter.call(this.vm, this.vm)
window.target = undefined
return value
}
update() {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
// 解析简单路径
const bailRE = /[^\w.$]/
export function parsePath(path) {
if (bailRE.test(path)) return
// data.a.b.c => [data,a,b,c]
const segment = path.split('.')
return function (obj) {
for (let index = 0; index < segment.length; index++) {
if (!obj) return
const item = segment[index]
obj = obj[item]
}
return obj
}
}
递归把 obj
的所有转化为响应式数据
class Observer {
constructor(value) {
this.value = value
if (!Array.isArray(value)) {
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj)
for (let index = 0; index < keys.length; index++) {
const key = keys[index]
defineReactive(obj, key, obj[key])
}
}
}
修改defineReactive
方法,将一个正常的 object
转化为被侦测的 object
,其中要判断数据类型,只有是 Object
类型的数据才会调用 walk
方法将每一个属性转化为 getter/setter
的形式来侦测变化
function defineReactive(data, key, value) {
// 递归所有的子属性
if (typeof val === 'object') {
new Observer(val)
}
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
dep.depend()
return value
},
set(newValue) {
if (val === newValue) return
val = newValue
dep.notify()
}
})
}
关于 Object 中的问题
由于前面的追踪方式局限,导致有些语法即使是数据发生了变化,数据也不会更新
new Vue({
el: '#app',
template: '<div>xxxx</div>',
methods:{
actions:{
this.obj.name = 'nelsen'
}
},
data:{
obj:{
}
}
})
action 方法中,我们在 obj 上面新增了一个 name 的属性,框架是无法侦测到这个变化的,
所以就不会向依赖发送通知。同理删除一个属性也是同样的
new Vue({
el: '#app',
template: '<div>xxxx</div>',
methods:{
actions:{
delete this.obj.name
}
},
data:{
obj:{
name:"nelsen"
}
}
})
vue2
中通过Object.defineProperty
来将对象的 key
转化为getter/setter
的形式来追踪依赖,但是getter/setter
只能追踪到数据是否被修改,无法追踪新增和删除
属性,这是由于js
语言的限制,没有提供元编程的能力
为了解决这个问题,框架给我们提供了vm.$set
和 vm.$delete
两个api
,来处理对象的新增删除属性。