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) {
// 做点什么
}
)
内容版权声明:除非注明,否则皆为本站原创文章。

