共计 3906 个字符,预计需要花费 10 分钟才能阅读完成。
vm.$watch
用法:vm.$watch(expOrFn, callback, [options] )
,返回值为 unwatch
是一个函数用来取消观察;下面主要理解 options
中的两个参数 deep
和immediate
以及unwatch
Vue.prototype.$watch = function (expOrFn, cb, options) {
const vm = this
options = options || {}
const watcher = new Watcher(vm, expOrFn, cb, options)
if(options.immediate) {cb.call(vm, watcher,.value)
}
return function unwatchFn() {watcher.teardown()
}
}
immediate
从上面代码中可以看出当 immediate
为true
时,就会直接进行执行回调函数
unwatch
实现方式是:
- 将被访问到的数据
dep
收集到watchs
实例对象上,通过this.deps
存起来 - 将被访问到的数据
dep.id
收集到watchs
实例对象上,通过this.depIds
存起来 - 最后通过
watchs
实例对象的teardown
进行删除
class Watcher {constructor (vm, expOrFn, cb) {
this.vm = vm
this.deps = []
this.depIds = new Set()
if(typeof expOrFn === 'function') {this.getter = expOrFn}else {this.getter = parsePath(expOrFn)
}
this.cb = cb
this.value = this.get()}
....
addDep (dep) {
const id = dep.id // 参数 dep 是 Dep 实例对象
if(!this.depIds.has(id)) { // 判断是否存在避免重复添加
this.depIds.add(id)
this.deps.push(dep)
dep.addSub(this) //this 是依赖
}
}
teardown () {
let i = this.deps.length
while (i--) {this.deps[i].removeSub(this)
}
}
}
let uid = 0
class Dep {constructor () {
this.id = uid++
...
}
...
depend () {if(window.target) {window.target.addDep(this) // 将 this 即当前 dep 对象加入到 watcher 对象上
}
}
removeSub (sub) {const index = this.subs.indexOf(sub)
if(index > -1) {return this.subs.splice(index, 1)
}
}
}
分析
当执行 teardown()
时需要循环;因为例如expOrFn = function () { return this.name + this.age}
,这时会有两个dep
分别是 name
与age
分别都加入了 watcher
依赖 (this
),都会加入到this.deps
中,所以需要循环将含有依赖的 dep
都删除其依赖
deep
需要明白的是
-
deep
干啥用的,例如data = {arr: [1, 2, {b: 6]}
,当我们只是监听data.arr
时,在[1, 2, {b: 66}]
这个数值内部发生变化时,也需要触发,即b = 888
怎么做呢?
class Watcher {constructor (vm, expOrFn, cb, options) {
this.vm = vm
this.deps = []
this.depIds = new Set()
if(typeof expOrFn === 'function') {this.getter = expOrFn}else {this.getter = parsePath(expOrFn)
}
if(options) { // 取值
this.deep = !!options.deep
}else {this.deep = false}
this.cb = cb
this.value = this.get()}
get () {
window.target = this
let value = this.getter.call(vm, vm)
if(this.deep) {traverse(value)
}
window.target = undefined
return value
}
...
}
const seenObjects = new Set()
function traverse (val) {_traverse(val, seenObjects)
seenObjects.clear()}
function _traverse(val, seen) {
let i, keys
const isA = Array.isArray(val)
if((!isA && isObject(val)) || Object.isFrozen(val)) { // 判断 val 是否是对象或者数组以及是否被冻结
return
}
if(val._ob_) {const depId = val._ob_.dep.id // 可以看前面一篇我们对 Observer 类添加了 this.dep = new Dep(),所以能访问其 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[i], seen)
}
}
分析
-
window.target = this
,寄存依赖 -
let value = this.getter.call(vm, vm)
访问当前 val,并执行get
的 dep.depend()
,如果发现val
为数组,则将依赖加入到 observer
的dep
中,也就实现了对当前数组的拦截
-
traverse(value)
也就是执行_traverse(val, seenObjects)
;核心就是对被Observer
的val
通过val[i]
通过这种操作,间接触发get
,将依赖添加到当前数值的dep
中,这样也就实现了,当内部数据发生变化,也会循环subs
执行依赖的update
,从而触发回调;当是数组时,只需进行遍历,看内部是否有Object
对象即可,因为在第二步的时候,会对val
进行判断是否是数组,变改变七个方法的 value,在遍历;所以这边只要是内部数组都会进行拦截操作,添加依赖,即对象{}
这种没没添加依赖。 -
seenObjects.clear()
当内部所以类型数据都添加好其依赖后,就清空。 -
window.target = undefined
消除依赖
vm.$set
用法:vm.$set(target, key, value)
作用
- 对于数组,进行
set
则是添加新元素,并需要触发依赖更新 - 对于对象,如果
key
值存在,则是修改value
;不存在,则是添加新元素,需新元素要进行响应式处理,以及触发更新 - 对于对象本身不是响应式,则直接添加
key-value
,无需处理
Vue.prototype.$set = function (target, key, val) {if(Array.isArray(target) && isValidArrayIndex(key)) { // 是数组并且 key 有效
target.length = Math.max(target.length, key) // 处理 key > target.length
target.splice(key, 1, val) // 添加新元素,并输出依赖更新同时新元素也会进行 `Obsever` 处理
return val
}
if(key in targert && !(key in Object.prototype) { // 能遍历并且是自身 key
target[key] = val // 触发 set,执行依赖更新
return val
}
const ob = target._ob_
if(target.isVue || (ob && ob.vm.Count) {// 不是 vue 实例也不是 vue 实例的根对象(即不是 this.$data 跟对象)
// 触发警告
return
}
if(!ob) { // 只添加
target[key] = val
return val
}
defineReactive(ob.value, key, val) // 进行响应式处理
ob.dep.notify() // 触发依赖更新
returnv val
}
vm.$delete
用法:vm.$delete(target, key)
作用
- 对于数组,进行
delete
则是删除新元素,并需要触发依赖更新 - 对于对象,如果
key
值不存在,直接return
,存在,删除元素, - 对于对象本身不是响应式,则只删除
key-value
,无需其他处理
Vue.prototype.$delete = function (target, key) {if(Array.isArray(target) && isValidArrayIndex(key)) {target.splice(key, 1)
return
}
const ob = target._ob_
if(target.isVue || (ob && ob.vm.Count) {// 不是 vue 实例也不是 vue 实例的根对象(即不是 this.$data 跟对象)
// 触发警告
return
}
if(!hasOwn(target, key)) {return}
delete target[key]
if(!ob) {return}
ob.dep.notify()}
掘金地址
正文完
发表至: javascript
2019-05-22