Vue偵測相關api的實現(xiàn)方法
vm.$watch
用法: vm.$watch( expOrFn, callback, [options] ) ,返回值為 unwatch 是一個函數(shù)用來取消觀察;下面主要理解 options 中的兩個參數(shù) 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 時,就會直接進行執(zhí)行回調(diào)函數(shù)
unwatch
實現(xiàn)方式是:
- 將被訪問到的數(shù)據(jù) dep 收集到 watchs 實例對象上,通過 this.deps 存起來
- 將被訪問到的數(shù)據(jù) 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 //參數(shù)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)
}
}
}
分析
當執(zhí)行 teardown() 時需要循環(huán);因為例如 expOrFn = function () { return this.name + this.age } ,這時會有兩個 dep 分別是 name 與 age 分別都加入了 watcher 依賴( this ),都會加入到 this.deps 中,所以需要循環(huán)將含有依賴的 dep 都刪除其依賴
deep
需要明白的是
- deep 干啥用的,例如 data = {arr: [1, 2, {b: 6]} ,當我們只是監(jiān)聽 data.arr 時,在 [1, 2, {b: 66}] 這個數(shù)值內(nèi)部發(fā)生變化時,也需要觸發(fā),即 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是否是對象或者數(shù)組以及是否被凍結
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,并執(zhí)行 get
的 dep.depend() ,如果發(fā)現(xiàn) val 為數(shù)組,則將依賴加入到 observer 的 dep 中,也就實現(xiàn)了對當前數(shù)組的攔截
- traverse(value) 也就是執(zhí)行 _traverse(val, seenObjects) ;核心就是對被 Observer 的 val 通過 val[i] 通過這種操作,間接觸發(fā) get ,將依賴添加到當前數(shù)值的 dep 中,這樣也就實現(xiàn)了,當內(nèi)部數(shù)據(jù)發(fā)生變化,也會循環(huán) subs 執(zhí)行依賴的 update ,從而觸發(fā)回調(diào);當是數(shù)組時,只需進行遍歷,看內(nèi)部是否有 Object 對象即可,因為在第二步的時候,會對 val 進行判斷是否是數(shù)組,變改變七個方法的value,在遍歷;所以這邊只要是內(nèi)部數(shù)組都會進行攔截操作,添加依賴,即對象 {} 這種沒沒添加依賴。
- seenObjects.clear() 當內(nèi)部所以類型數(shù)據(jù)都添加好其依賴后,就清空。
- window.target = undefined 消除依賴
vm.$set
用法: vm.$set(target, key, value)
作用
- 對于數(shù)組,進行 set 則是添加新元素,并需要觸發(fā)依賴更新
- 對于對象,如果 key 值存在,則是修改 value ;不存在,則是添加新元素,需新元素要進行響應式處理,以及觸發(fā)更新
- 對于對象本身不是響應式,則直接添加 key-value ,無需處理
Vue.prototype.$set = function (target, key, val) {
if(Array.isArray(target) && isValidArrayIndex(key)) { //是數(shù)組并且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 //觸發(fā)set,執(zhí)行依賴更新
return val
}
const ob = target._ob_
if(target.isVue || (ob && ob.vm.Count) { //不是vue實例也不是vue實例的根對象(即不是this.$data跟對象)
//觸發(fā)警告
return
}
if(!ob) { //只添加
target[key] = val
return val
}
defineReactive(ob.value, key, val) //進行響應式處理
ob.dep.notify() //觸發(fā)依賴更新
returnv val
}
vm.$delete
用法: vm.$delete( target, key)
作用
- 對于數(shù)組,進行 delete 則是刪除新元素,并需要觸發(fā)依賴更新
- 對于對象,如果 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跟對象)
//觸發(fā)警告
return
}
if(!hasOwn(target, key)) {
return
}
delete target[key]
if(!ob) {
return
}
ob.dep.notify()
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Vue使用json-server進行后端數(shù)據(jù)模擬功能
這篇文章主要介紹了Vue使用json-server進行后端數(shù)據(jù)模擬功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-04-04
深入淺出 Vue 系列 -- 數(shù)據(jù)劫持實現(xiàn)原理
深入淺出 Vue 系列 -- 數(shù)據(jù)劫持實現(xiàn)原理2019-04-04

