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) {
 // 做点什么
 }
)

      

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

转载注明出处:https://www.heiqu.com/19.html