vue 计算属性和data_Vue 源码阅读十五 - 计算属性和侦听属性

computed

在初始化 Vue 实例阶段会执行

initState 处理 data、props、computed、watcher 数据属性,其中在 initComputed 是处理

computed 的方法,它的定义在 src/core/instance/state.js 中:

首先创建 vm._computedWatchers 为一个空对象,接着对 computed 对象做遍历,拿到计算属性的每一个 userDef,然后尝试获取这个 userDef 对应的 getter 函数。

接下来为每一个

getter 创建一个 watcher,这个 watcher 和渲染 watcher 有一点很大的不同,它是一个 computed

watcher,因为 const computedWatcherOptions = { lazy: true }。

最后对判断如果 key 不是 vm 的属性,则调用 defineComputed(vm, key, userDef)。接下来我们看一下 defineComputed 的实现:

defineComputed 方法中会将每个计算属性通过 Object.defineProperty 定义到 vm

实例上,重点关注这里为每个计算属性定义的 getter 和 setter 方法,平时计算属性有 setter 的情况比较少,我们重点关注一下

getter 部分, getter 对应的是 createComputedGetter(key) 的返回值,来看一下它的定义:

可以看到将 computedGetter 作为属性的 getter 方法,当访问这个计算属性时,会拿到 watcher.value 的值,即拿到在上面 initComputed 中 getter 方法的执行结果。

整个计算属性的初始化过程到此结束。

watch

侦听属性的初始化也是发生在 Vue 的实例初始化阶段的 initState 函数中,通过 initWatch 函数去处理:

首先对

watch 对象做遍历,拿到每一个 handler,因为 Vue 是支持 watch 的同一个 key 对应多个 handler,所以如果

handler 是一个数组,则遍历这个数组,调用 createWatcher 方法,否则直接调用 createWatcher:

这里调用 vm.$watch(keyOrFn, handler, options) 函数,$watch 是 Vue 原型上的方法,它是在执行 stateMixin 的时候定义的:

也就是说,侦听属性 watch 最终会调用 $watch 方法,这个方法首先判断 cb 如果是一个对象,则调用 createWatcher 方法,这是因为 $watch 方法是用户可以直接调用的,它可以传递一个对象,也可以传递函数。

接着执行 const watcher = new Watcher(vm, expOrFn, cb, options) 实例化了一个 watcher,这里需要注意一点这是一个 user watcher,因为 options.user = true。

通过实例化

watcher 的方式,一旦我们 watch 的数据发送变化,它最终会执行 watcher 的 run 方法,执行回调函数

cb,并且如果我们设置了 immediate 为 true,则直接会执行回调函数 cb。最后返回了一个 unwatchFn 方法,它会调用

teardown 方法去移除这个 watcher。

所以本质上侦听属性也是基于 Watcher 实现的,它是一个 user watcher。其实 Watcher 支持了不同的类型,下面我们梳理一下它有哪些类型以及它们的作用。

Watcher options

Watcher 的构造函数对 options 做的了处理,代码如下:

所以 watcher 总共有 4 种类型,我们来一一分析它们,看看不同的类型执行的逻辑有哪些差别。

deep watcher

通常,如果我们想对一下对象做深度观测的时候,需要设置这个属性为 true,考虑到这种情况:

当我们修改

b 属性时发现没有任何打印,因为我们是 watch 了 a 对象,只触发了 a 的 getter,并没有触发 a.b 的

getter,所以并没有订阅它的变化,当我们执行 vm.a.b = 2 赋值的时候,虽然触发了

setter,但没有可通知的对象,所以也并不会触发 watch 的回调函数了。

而我们只需要对代码做稍稍修改,就可以观测到这个变化了:

这样就创建了一个 deep watcher 了,在 watcher 执行 get 求值的过程中有一段逻辑:

在对 watch 的表达式或者函数求值后,会调用 traverse 函数,它的定义在 src/core/observer/traverse.js 中:

traverse

的逻辑也很简单,它实际上就是对一个对象做深层递归遍历,因为遍历过程中就是对一个子对象的访问,会触发它们的 getter

过程,这样就可以收集到依赖,也就是订阅它们变化的 watcher,这个函数实现还有一个小的优化,遍历过程中会把子响应式对象通过它们的 dep

id 记录到 seenObjects,避免以后重复访问。

那么在执行了 traverse 后,我们再对 watch 的对象内部任何一个值做修改,也会调用 watcher 的回调函数了。

user watcher

通过 vm.$watch 创建的 watcher 是一个 user watcher,而 user watcher 主要作用是在对 watcher 求值以及在执行回调函数的时候,当出现错误时,会调用 handleError 处理错误。

computed watcher

computed watcher 就是上面专为计算属性而使用的 watcher。

sync watcher

当响应式数据发送变化后,触发了

watcher.update(),只是把这个 watcher 推送到一个队列中,在 nextTick 后才会真正执行 watcher

的回调函数。而一旦我们设置了 sync,就可以直接调用 run() 来同步执行 watcher 的回调函数。

只有当我们需要 watch 的值的变化到执行 watcher 的回调函数是一个同步过程的时候才会去设置该属性为 true。作者:明里人

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值