vue核心面试题(Watch中的deep:true 是如何实现的 )
概念:这儿的watch就是用户写的watch方法,在写watch时有两个属性一个immediate是用来确定是否立即执行方法的,另一个属性就是下来要讨论的deep,当它为true就会实现深度监听,今天我们是需求知道它怎么实现的?computed为什么没有deep:true?因为computed是用在模板里的,在模板中的数据会调一个方法JSON.strginify(),放一个对象,默认会对这个对象里
概念:
这儿的watch就是用户写的watch方法,在写watch时有两个属性一个immediate是用来确定是否立即执行方法的,另一个属性就是下来要讨论的deep,当它为true就会实现深度监听,今天我们是需求知道它怎么实现的?
当用户指定了 watch 中的deep属性为 true 时,如果当前监控的值是数组类型。会对对象中的每 一项进行求值,此时会将当前 watcher 存入到对应属性的依赖中,这样数组中对象发生变化时也 会通知数据更新。
computed为什么没有deep:true?因为computed是用在模板里的,在模板中的数据会调一个方法JSON.strginify(),放一个对象,默认会对这个对象里的所有属性求值。
原理:
调用initWatch初始化watch时传入的是一个对象,在initWatch中会调用createWatcher方法,里面有一个核心方法vm.$watch(),会传入三个参数第一个就是我们要监听的属性,第二参就是要执行的函数,第三参就是watch传入的属性,在这个$watch中会创建一个watcher实例,在watcher中会对传入的属性进行判断,如果expOrFn是函数,就直接给了getter,如果不是会将它会将这个expOrFn转成函数,因为这是用户定义的watch不是计算属性所以会调用watcher中的get方法,在get中最先将watcher挂载在了全局上,这时候就会执行用户的方法,进行依赖收集(这儿说一下它是怎么进行的依赖收集,在调用render函数触发dom中响应式变量的get的时候,在oberver中就有一行代码是如果Dep.target有watcher了就执行dep.depend()这个就会把watcher中依赖的属性全部追加依赖)当收集的属性变化了,watcher就会更新。这篇文章的核心点来了:如果用户要监听的是个对象,内层的对象就不会被依赖收集,这时候有一个deep属性,要想让收集到内层的对象,就需要将deep设置成true,这时候就会执行traverse这个方法,这个方法里就是做了个数组递归,如果是数组的话,会根据数组的每一项索引取值,进行递归追加依赖,如果是对象会拿的key进行遍历取值,进行递归追加依赖,traverse就是deep:true实现的核心。这样就会把数组或者对象的没一个属性都进行依赖追加进行监听,只有依赖发生变化就会通知视图更新。
源码:
1.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)
}
}
}
2.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]
}
return vm.$watch(expOrFn, handler, options)
}
3.$watch
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,表示与默认的渲染及计算属性没关系
// vm.$watch('msg',()=>{}) 这是msg就会传到expOrFn,用户传的函数就是这的cb
const watcher = new Watcher(vm, expOrFn, cb, options) // 创建了一个watcher
if (options.immediate) { // 如果用户传入的immediate是true,就会立即执行传入的方法
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
}
4.new Watcher中的部分代码
constructor (
vm: Component,
expOrFn: string | Function, // 这就是用户写在watch中要监听的属性
cb: Function, // 当前用户定义的函数
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
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()
: ''
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn) // 如果expOrFn是个字符串,会将这个expOrFn转成函数
// msg function(){return vm.msg}
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 // 这时候lazy是false会执行get方法
? undefined
: this.get()
}
5.get方法
get () {
pushTarget(this) // 取值之前将watcher放到全局上,这个做了一个Dep.target = this操作
// Dep.target只是一个标记,存储当前的watcher实例
let value
const vm = this.vm
try { // 默认将expOrFn进行取值,这时候就将watcher收集起来了,当收集的属性变化了,watcher就会更新
//如果expOrFn的值是一个对象,内层的对象就不会被依赖收集,要想让收集到内层的对象,
//就需求把这个对象循环一遍,在循环的过程中就会把里面的watcher也收集起来
value = this.getter.call(vm, vm) // 执行用户传入的方法,进行依赖收集
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) { // 如果deep是true会执行traverse方法
traverse(value)
}
popTarget() // 进来之后把当前的watcher删除,Dep.target标记为上一个watcher
this.cleanupDeps()
}
return value
}
export function pushTarget (target: ?Watcher) {
targetStack.push(target) // 在触发get的时候,将当前watcher存入targetStack中
Dep.target = target // Dep.target存储为当前watcher的实例
}
export function popTarget () {
targetStack.pop() // 在取值后将当前watcher删除
Dep.target = targetStack[targetStack.length - 1] // Dep.target存储为当前上一个watcher
}
在get方法中,进来执行了pushTarget将当前watcher放在了targetStack里,然后在最后又将当前watcher从targetStack移除,我认为这儿做了个操作就是为了更改Dep.target的标记的,就是标记了当前的watcher,在标记后取值的时候对当前watcher进行依赖追加,取值追加依赖后又把Dep.target的标记改为初始的,初始的就是null,targetStack初始空的数组,在每次执行get对targetStack追加后又移除,我认为这个targetStack里面最多就一个元素,用完又删了,没太明白这样写的用意,有哪个大佬看明白了,评论区讲一下?
6.traverse
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) // 如果是对象会拿的key进行遍历取值,只有一取值就会把当前watcher存起来
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
更多推荐
所有评论(0)