Hi 大家好,假期高兴鸭~ 咳咳,在后面两篇咱们从设计登程讲了一下 Vue2 的响应式原理和实现,还有计算属性的具体解析等等。这一篇呢就是这个系列的最初一篇了,咱们来聊一下侦听属性和 vm.$watch
,再回到设计来总结一下 Vue2 的响应式。如果没有看过后面两篇的敌人先看了后面的再来哈,传送门:Vue2 响应式原理解析(一):从设计登程,Vue2 响应式原理解析(二):计算属性揭秘。
侦听属性 watch
对于侦听属性的应用我就不多了,置信大家都很纯熟,无非就是定义个函数,当要监听的值发生变化时会回调这个函数。咱们间接来康康要害代码是怎么实现的。关上 src/core/instance/state.js
文件,找到 initState
:
export function initState (vm: Component) {vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {initData(vm)
} else {observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch) // 侦听属性的初始化是在最初。}
}
这个函数外面能够看到相熟的 initData
和 initComputed
,后面曾经讲过了。initWatch
就是侦听属性初始化的函数了,这里留神一下 initWatch
是在最初调用的并传入了 vm 对象(其实就是要监听 vm 上的属性), 这意味着侦听属性也是能够侦听到计算属性的变动哟 。initWatch
的内容很简略,咱们只看要害的代码:
function initWatch (vm: Component, watch: Object) {for (const key in watch) {const handler = watch[key]
if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i])
}
} else {
// 看这里。createWatcher(vm, key, handler)
}
}
}
initWatch
遍历咱们定义的 watch 对象属性,拿到每个属性的侦听函数 handler,并对其调用 createWatcher
:
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {handler = vm[handler]
}
// 其实侦听属性最初就是用 $watch api 实现的。return vm.$watch(expOrFn, handler, options)
}
到这里咱们发现原来侦听属性就是利用 vm.$watch 来实现的啦。
vm.$watch
$watch 函数是能够在 Vue 对象上调用的,所以定义在了 Vue 对象的原型上,具体在 stateMixin
函数中:
export function stateMixin (Vue: Class<Component>) {
// ...
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// ...
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
// 立刻求值
if (options.immediate) {
try {cb.call(vm, watcher.value)
} catch (error) {// ...}
}
return function unwatchFn () {watcher.teardown()
}
}
}
这里咱们看到 vm.$watch 其实是生成了 Watcher
对象,有这几个中央要康康:
expOrFn
,这里是须要监听的属性值。options.user
,表明是用户定义的,watcher 更新时会调用用户定义的回调函数 cb。options.immediate
,立刻求值,一开始就会传递以后值到定义的侦听函数。unwatchFn
,能够手动敞开监听(当然定义的侦听属性不须要这个了)。
expOrFn
这里我重点说一下 expOrFn
。expOrFn 是能够反对属性表达式的,依照 Vue 文档的说法:
察看 Vue 实例上的一个表达式或者一个函数计算结果的变动。
也就是说能够像这样去设置:
vm.$watch('a.b', function (newVal, oldVal) {// 属性 a 扭转或者 a.b 扭转都会触发})
vm.$watch(function () {return this.c + this.c},
function (newVal, oldVal) {// 属性 c 扭转或者 d 扭转都会触发}
)
第一种状况下 expOrFn 是表达式。在 src/core/observer/watcher.js
中找到 Watcher
的构造函数:
// ...
if (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)
// ...
}
// ...
咱们晓得 watcher.getter 要求是个函数,parsePath
在 src/core/util/lang.js
外面:
export function parsePath (path: string): any {
// ...
const segments = path.split('.')
return function (obj) {for (let i = 0; i < segments.length; i++) {if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
parsePath
返回的的确是一个函数,内容是依照表达式的门路程序去逐渐取得门路上的属性(对象)以及最初的值。这里咱们回顾下 defineReactive
,按程序去拜访表达式门路上的属性会触发属性的 get
,这样就建设了依赖关系(门路上的所有属性都会),当波及的属性变动时就会告诉 watcher 了~ 同理第二种状况下是一个函数也是一样的。剩下的怎么去告诉 watcher 更新后面第一篇曾经介绍过了,这里就不多说了。
设计总结
到这里 Vue2 响应式的次要内容解析就完结了,咱们看了很多的代码,是时候来总结一下啦。不晓得大家看源码学习的目标和感触是什么,我呢次要是关注作者的设计用意和衡量侧重点,当然还有一些实现的技巧之类。第一篇咱们从设计登程,最初呢当然也要从设计来总结一下从 Vue2 响应式中学到了什么。
可重用的模块
Vue2 响应式实现的一个很赞的中央是把这套货色独立了进去,形象出了 Dep
和 Watcher
等要害定义,基本上如果你是 Web 利用都能够间接拿去用。这通知咱们在实现之前要多思考重用和形象,这样你的实现能力施展更大的价值。
奇妙的双向依赖设计与实现
在观察者模式中,个别咱们只会在被观察者上记录观察者列表,等到须要时去告诉观察者即可。而在 Vue2 中因为应用场景下依赖关系会发生变化(依赖关系收集在求值过程中),所以采纳了双向依赖的设计与实现。这通知咱们设计模式不是死的,依据你想达到的目标去做灵便调整,我感觉这是 Vue2 响应式设计十分精彩的一个中央~
观察者模式的角色权重
从 Vue2 的设计和实现中咱们能够看到,观察者模式中被观察者(属性和 Dep
)和观察者(Watcher
)的角色权重是不同的,从代码量也能够感触进去。被观察者次要是实现依赖的建设和告诉机制,更形象一些;而观察者则要依据理论场景退出更多的功能设计与实现,比方 Vue 中的计算属性缓存和 expOrFn 等。这也是揭示咱们在实现本人的观察者模式场景中,具体的内容应该放在哪里有个参考。
最初
到这里 Vue2 响应式我想哔哔的货色曾经全副讲完了,心愿能给大家一点参考吧,程度无限,了解和解读难免会有错漏,欢送指出哇。最近 Vue3.0 One Piece 也正式公布了,当前偶也会继续写一些 Vue3 的相干东东,下次再见~
欢送 star 和关注我的 JS 博客:小声比比 Javascript