Vue源码解析之数据响应系统的使用(2)
现在仅能够观测a一个属性,为了能够观测对象data上面的所有属性,我们将定义访问器属性的那段代码封装一下:
function walk () { for (let key in data) { const dep = []; const val = data[key]; Object.defineProperty(data, key, { set (newVal) { if (newVal === val) return; val = newVal; dep.forEach(fn => fn()); }, get () { // Target就是该变量的依赖函数 dep.push(Target); return val; } }) } }
用for循环遍历data上的所有属性,对每一个属性都用Object.defineProperty改为访问器属性。
现在监测data里面基本类型值的属性没问题了,如果data的属性值又是一个对象呢:
data: { a: { aa: 1 } }
我们再来改一下我们的walk函数,当val还是一个对象时,递归调用walk:
function walk (data) { for (let key in data) { const dep = []; const val = data[key]; // 如果val是对象,递归调用walk,将其属性转为访问器属性 if (Object.prototype.toString.call(val) === '[object Object]') { walk(val); } Object.defineProperty(data, key, { set (newVal) { if (newVal === val) return; val = newVal; dep.forEach(fn => fn()); }, get () { // Target就是该变量的依赖函数 dep.push(Target); return val; } }) } }
添加了一段判断逻辑,如果某个属性的属性值仍然是对象,就递归调用walk函数。
虽然经过上面的改造,data.a.aa是访问器属性了,但下面但代码仍然不能运行:
watch('a.aa', () => { console.log('修改了 a.b') })
这是为什么呢?再看我们的watch函数:
function watch (exp, fn) { // 将fn赋值给Target Target = fn; // 读取属性,触发get函数,收集依赖 data[exp]; }
在读取属性的时候是data[exp],放到这里就是data[a.aa],这自然是不对的。正确的读取方式应该是data[a][aa]. 我们需要对watch函数做改造:
function watch (exp, fn) { // 将fn赋值给Target Target = fn; let obj = data; if (/\./.test(exp)) { const path = exp.split('.'); path.forEach(p => obj = obj[p]) return; } data[exp]; }
这里增加了一个判断逻辑,当监测的字段中包含.时,就执行if语句块的内容。首先使用split函数将字符串转换为数组:a.aa => [a, aa]. 然后使用循环读取到嵌套的属性值,并且return结束。
Vue中提供了$watch实例方法来观测表达式,对复杂的表达式用函数取代:
// 函数 vm.$watch( function () { // 表达式 `this.a + this.b` 每次得出一个不同的结果时 // 处理函数都会被调用。 // 这就像监听一个未被定义的计算属性 return this.a + this.b }, function (newVal, oldVal) { // 做点什么 } )
内容版权声明:除非注明,否则皆为本站原创文章。