浅谈Vue数据响应

Vue 中可以用 $watch 实例方法观察一个字段,当该字段的值发生变化时,会执行指定的回调函数(即观察者),实际上和 watch 选项作用相同。如下:

vm.$watch('box', () => { console.log('box变了') }) vm.box = 'newValue' // 'box变了'

以上例切入,我想实现一个功能类似的方法 myWatch。

如何知道我观察的属性被修改了?

—— Object.defineProperty 方法

该方法可以为指定对象的指定属性设置 getter-setter 函数对,通过这对 getter-setter 可以捕获到对属性的读取和修改操作。示例如下:

const data = { box: 1 } Object.defineProperty(data, 'box', { set () { console.log('修改了 box') }, get () { console.log('读取了 box') } }) console.log(data.box) // '读取了 box' // undefined data.box = 2 // '修改了 box' console.log(data.box) // '读取了 box' // undefined

如此,便拦截到了对 box 属性的修改和读取操作。

但 res 为 undefined,data.box = 2 的修改操作也无效。

get 与 set 函数功能不健全

故修改如下:

const data = { box: 1 } let value = data.box Object.defineProperty(data, 'box', { set (newVal) { if (newVal === value) return value = newVal console.log('修改了 box') }, get () { console.log('读取了 box') return value } }) console.log(data.box) // '读取了 box' // 1 data.box = 2 // '修改了 box' console.log(data.box) // '读取了 box' // 2

有了这些, myWatch 方法便可实现如下:

const data = { box: 1 } function myWatch(key, fn) { let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal fn() }, get () { return value } }) } myWatch('box', () => { console.log('box变了') }) data.box = 2 // 'box变了'

但存在一个问题,不能给同一属性添加多个依赖(观察者):

myWatch('box', () => { console.log('我是观察者') }) myWatch('box', () => { console.log('我是另一个观察者') }) data.box = 2 // '我是另一个观察者'

后面的依赖(观察者)会将前者覆盖掉。

如何能够添加多个依赖(观察者)?

—— 定义一个数组,作为依赖收集器:

const data = { box: 1 } const dep = [] function myWatch(key, fn) { dep.push(fn) let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach((f) => { f() }) }, get () { return value } }) } myWatch('box', () => { console.log('我是观察者') }) myWatch('box', () => { console.log('我是另一个观察者') }) data.box = 2 // '我是观察者' // '我是另一个观察者'

修改 data.box 后,两个依赖(观察者)都执行了。

若上例 data 对象需新增两个能够响应数据变化的属性 foo bar:

const data = { box: 1, foo: 1, bar: 1 }

只需执行以下代码即可:

myWatch('foo', () => { console.log('我是foo的观察者') }) myWatch('bar', () => { console.log('我是bar的观察者') })

但问题是,不同属性的依赖(观察者)都被收集进了同一个 dep,修改任何一个属性,都会触发所有的依赖(观察者):

data.box = 2 // '我是观察者' // '我是另一个观察者' // '我是foo的观察者' // '我是bar的观察者'

我想可以这样解决:

const data = { box: 1, foo: 1, bar: 1 } const dep = {} function myWatch(key, fn) { if (!dep[key]) { dep[key] = [fn] } else { dep[key].push(fn) } let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep[key].forEach((f) => { f() }) }, get () { return value } }) } myWatch('box', () => { console.log('我是box的观察者') }) myWatch('box', () => { console.log('我是box的另一个观察者') }) myWatch('foo', () => { console.log('我是foo的观察者') }) myWatch('bar', () => { console.log('我是bar的观察者') }) data.box = 2 // '我是box的观察者' // '我是box的另一个观察者' data.foo = 2 // '我是foo的观察者' data.bar = 2 // '我是bar的观察者'

但实际上这样更好些:

const data = { box: 1, foo: 1, bar: 1 } let target = null for (let key in data) { const dep = [] let value = data[key] Object.defineProperty(data, key, { set (newVal) { if (newVal === value) return value = newVal dep.forEach(f => { f() }) }, get () { dep.push(target) return value } }) } function myWatch(key, fn) { target = fn data[key] } myWatch('box', () => { console.log('我是box的观察者') }) myWatch('box', () => { console.log('我是box的另一个观察者') }) myWatch('foo', () => { console.log('我是foo的观察者') }) myWatch('bar', () => { console.log('我是bar的观察者') }) data.box = 2 // '我是box的观察者' // '我是box的另一个观察者' data.foo = 2 // '我是foo的观察者' data.bar = 2 // '我是bar的观察者'

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/2f1d3a6d0ce859cd3984f9f3c3c28e45.html