接下来重点来看Vue的数据响应系统。我看很多文章在讲数据响应的时候先用一个简单的例子介绍了数据双向绑定的思路,然后再看源码。这里也借鉴了这种方式,感觉这样的确更有利于理解。
数据双向绑定的思路
1. 对象
先来看元素是对象的情况。假设我们有一个对象和一个监测方法:
const data = { a: 1 }; /** * exp[String, Function]: 被观测的字段 * fn[Function]: 被观测对象改变后执行的方法 */ function watch (exp, fn) { }
我们可以调用watch方法,当a的值改变后打印一句话:
watch('a', () => { console.log('a 改变了') })
要实现这个功能,我们首先要能知道属性a被修改了。这时候就需要使用Object.defineProperty函数把属性a变成访问器属性:
Object.defineProperty(data, 'a', { set () { console.log('设置了 a') }, get () { console.log('读取了 a') } })
这样当我们修改a的值:data.a = 2时,就会打印出设置了 a, 当我们获取a的值时:data.a, 就会打印出读取了 a.
在属性的读取和设置中我们已经能够进行拦截并做一些操作了。可是在属性修改时我们并不想总打印设置了 a这句话,而是有一个监听方法watch,不同的属性有不同的操作,对同一个属性也可能监听多次。
这就需要一个容器,把对同一个属性的监听依赖收集起来,在属性改变时再取出依次触发。既然是在属性改变时触发依赖,我们就可以放在setter里面,在getter中收集依赖。这里我们先不考虑依赖被重复收集等一些情况
const dep = []; Object.defineProperty(data, 'a', { set () { dep.forEach(fn => fn()); }, get () { dep.push(fn); } })
我们定义了容器dep, 在读取a属性时触发get函数把依赖存入dep中;在设置a属性时触发set函数把容器内的依赖挨个执行。
那fn从何而来呢?再看一些我们的监测函数watch
watch('a', () => { console.log('a 改变了') })
该函数有两个参数,第一个是被观测的字段,第二个是被观测字段的值改变后需要触发的操作。其实第二个参数就是我们要收集的依赖fn。
const data = { a: 1 }; const dep = []; Object.defineProperty(data, 'a', { set () { dep.forEach(fn => fn()); }, get () { // Target就是该变量的依赖函数 dep.push(Target); } }) let Target = null; function watch (exp, fn) { // 将fn赋值给Target Target = fn; // 读取属性,触发get函数,收集依赖 data[exp]; }
内容版权声明:除非注明,否则皆为本站原创文章。