数据要在获取时收集依赖,在设置时触发更新依赖,核心方法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) returnval = newValuedep.notify()}})
}
依赖谁谁?就是上面的 window.target
,那么他到底是啥?其实就是要通知用到这个状态的地方,
它有可能是个模块,也有能是一个 watch
,那么我们就需要封装这样一个集中处理不同依赖类型的方法,
我们先通知这个方法,然后这个方法内部再去通知其他地方。Watcher
class Watcher {constructor(vm, expOrFn, cb) {this.vm = vmthis.getter = parsePath(expOrFn)this.cb = cbthis.value = this.get()}get() {window.target = thislet value = this.getter.call(this.vm, this.vm)window.target = undefinedreturn value}update() {const oldValue = this.valuethis.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) returnconst item = segment[index]obj = obj[item]}return obj}
}
递归把 obj
的所有转化为响应式数据
class Observer {constructor(value) {this.value = valueif (!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) returnval = newValuedep.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
,来处理对象的新增删除属性。