接下来一段时间深入学习一下vue相关的原理,加油。
在beforeCreate
之后,created
之前,会initState
,在initState
中会调用initWatch
。
function initState(vm) {
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 {
observer(vm._data={}, true);
}
if (opts.computed) initMethods(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
// 初始化watch
initWatch(vm, opts.watch);
}
}
function initWatch (vm: Component, watch: Object) {
// 1.遍历watch
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 {
// 2.创建watcher
createWatcher(vm, key, handler)
}
}
}
这段代码只是简单的进行遍历,然后每个watch
都使用createWater
进行处理。
createWatcher
函数接收四个参数,该函数内部其实就是从用户合起来传入的对象中把回调函数cb
和参数options
剥离出来,然后再以常规的方式调用$watch
方法并将剥离出来的参数穿进去。
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
//监听属性的值是一个对象,包含handler,deep,immediate
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
//如果回调函数是一个字符串,从VM中获取
if (typeof handler === 'string') {
handler = vm[handler]
}
//expOrFn是key, options是watch的全部选项
return vm.$watch(expOrFn, handler, options)
}
vm.$watch
isPlainObject
方法是判断handler
是否为对象类型 Object
;
//获取值得原始类型字符串
const _toString = Object.prototype.toString
export function isPlainObject (obj: any): boolean {
return _toString.call(obj) === '[object Object]'
}
首先,会判断传入的回掉函数是不是一个对象,如果是,那么就表明用户是将第二个参数回掉函数和第三个参数options
结合起来传入的,那么此时就调用createWatcher
函数:
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
// 每一个watch都配发一个watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果immediate为true,立即执行监听回调
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
vm.$watch
做了主要的两件事
watch
配发watcher
;immediate
配置来判断是否立即执行监听回调export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
...
//此处只拿出了Watch类的构造方法;如需查看完整代码还请自行查阅
}
新建watcher的时候主要做了以下几件事情:
watch
中的cb
)options
当选项参数options
中的deep
属性为true
时,如何实现深度观察呢?
所谓深度观察,就是当obj
对象发生变化时我们会得到通知,通知当obj.a
属性发生变化时我们也要能得到通知,简单的说就是观察对象内部值的变化。
要实现这个功能也不难,我们知道,要想让数据变化时通知我们,那我们只需成为这个数据的依赖即可,因为数据变化时会通知它所有的依赖,那么如何成为数据的依赖呢,很简单,读取一下数据即可。也就是说我们只需在创建watcher
实例的时候把obj
对象内部所有的值都递归的读一遍,那么这个watcher
实例就会被加入到对象内所有值的依赖列表中,之后当对象内任意某个值发生变化时就能够得到通知了。
在创建watcher
实例的时候,会执行Watcher
类中get
方法来读取一下被观察的数据。
export default class Watcher {
constructor (/* ... */) {
// ...
this.value = this.get()
}
get () {
// ...
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
return value
}
}
在get
方法中,如果传入的deep===true
,则会调用traverse
函数。
const seenObjects = new Set()
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
// 如果是数组,循环遍历
i = val.length
while (i--) _traverse(val[i], seen)
} else {
// 对象
keys = Object.keys(val)
i = keys.length
// 递归遍历每一个属性
while (i--) _traverse(val[keys[i]], seen)
}
}
可以看到,该函数其实就是个递归遍历的过程,把被观察数据的内部值都递归遍历读取一遍。
首先先判断传入的val
类型,如果它不是Array或object
,再或者已经被冻结,那么直接返回,退出程序。
然后拿到val的dep.id
,存入创建好的集合seen
中,因为集合相比数据而言它有天然的去重效果,以此来保证存入的dep.id
没有重复,不会造成重复收集依赖。
接下来判断如果是数组,则循环数组,将数组中每一项递归调用_traverse
;如果是对象,则取出对象所有的key
,然后执行读取操作,再递归内部值。
这样,把被观察数据内部所有的值都递归的读取一遍后,那么这个watcher
实例就会被加入到对象内所有值的依赖列表中,之后当对象内任意某个值发生变化时就能够得到通知了。
总结一下,其实traverse方法很好理解,主要做的事情是:
watch-watcher
; watch-watcher
了;这样不管对象内多深的属性变化,都会通知到 watch-watcher
,于是这样就完成了深度监听;