導航
[深入01] 執(zhí)行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件循環(huán)
[深入05] 柯里化 偏函數(shù) 函數(shù)記憶
[深入06] 隱式轉換 和 運算符
[深入07] 瀏覽器緩存機制(http緩存機制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模塊化
[深入13] 觀察者模式 發(fā)布訂閱模式 雙向數(shù)據(jù)綁定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手寫Promise
[深入20] 手寫函數(shù)
[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI
[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程
[源碼] Redux React-Redux01
[源碼] axios
[源碼] vuex
[源碼-vue01] data響應式 和 初始化渲染
[源碼-vue02] computed 響應式 - 初始化,訪問,更新過程
[源碼-vue03] watch 偵聽屬性 - 初始化和更新
[源碼-vue04] Vue.set 和 vm.$set
[源碼-vue05] Vue.extend
[源碼-vue06] Vue.nextTick 和 vm.$nextTick
前置知識
一些單詞
somewhat:有點
( somewhat expensive operation 操作有點昂貴 )
teardown:卸載
使用案例
<template>
<div class="about">
<h1>This is a watch page</h1>
<div>count = {{count}}</div>
<button @click="changeCount">change count</button>
<br />
<br />
<div>immediate立即執(zhí)行:count1 = {{count1}}</div>
<button @click="changeCount1">change count1</button>
<br />
<br />
<div>deep深度觀測 - 遇到問題是新舊值一樣,可以用commputed做深拷貝:count2 = {{nestObj.count2}}</div>
<button @click="changeCount2">change count2</button>
<br />
<br />
<div>count3 = {{nestObj.count3}}</div>
<button @click="changeCount3">change count3</button>
<br />
<br />
<button @click="changeTestArr">change testArr</button>
<br />
<br />
<button @click="changeAll">改變所有數(shù)據(jù) - 驗證sync</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
count1: 1,
nestObj: {
count2: 2,
count3: 3
},
testArr: {
count4: 4,
count5: 5
},
testHandlerIsFunctionName: 6,
};
},
computed: {
deepCopyNestObj() {
return JSON.parse(JSON.stringify(this.nestObj))
}
},
watch: {
count: function(val, newVal) { // ---------------------------------- 函數(shù)
console.log(val, newVal);
},
count1: {
handler(v, oldv) {
console.log(v, oldv, "immediate立即執(zhí)行,不需要依賴變化", "后執(zhí)行");
},
immediate: true
},
nestObj: { // ------------------------------------------------------ 對象
handler(v, oldv) {
console.log(v.count2, oldv.count2, "sync再nextTick之前先執(zhí)行");
},
deep: true,
sync: true // 同步 先于 異步的watch執(zhí)行,默認是異步
},
deepCopyNestObj(newVal, oldVal) {
console.log(newVal.count2, oldVal.count2, 'deep深度觀測 - 遇到問題是新舊值一樣,可以用commputed做深拷貝')
},
"nestObj.count3": function() {
// 監(jiān)聽對象中的某個屬性,可以使用obj.xxx的字符串形式作為key
console.log("watch到了nestObj.count3");
},
testArr: [ // ------------------------------------------------------ 數(shù)組
function handler1() {
console.log(1111);
},
function handler2() {
console.log(2222);
}
],
testHandlerIsFunctionName: 'watchHandlerIsFnName' // --------------- 字符串
// watchHandlerIsFnName 是一個方法,在 methods 定義的方法
// 當 testHandlerIsFunctionName 變化,就會調用watchHandlerIsFnName方法
},
methods: {
watchHandlerIsFnName(v, oldV) {
console.log(v, oldV, 'watch對象的 handler 是一個字符串,即一個方法名')
},
changeCount() {
this.count = this.count + 1;
},
changeCount1() {
this.count1 = this.count1 + 1;
},
changeCount2() {
this.nestObj.count2 = this.nestObj.count2 + 1;
},
changeCount3() {
this.nestObj.count3 = this.nestObj.count3 + 1;
},
changeAll() {
this.count = this.count + 1;
this.count1 = this.count1 + 1;
this.nestObj.count2 = this.nestObj.count2 + 1;
this.nestObj.count3 = this.nestObj.count3 + 1;
this.testArr = this.testArr + 1;
this.testHandlerIsFunctionName = this.testHandlerIsFunctionName + 1
},
changeTestArr() {
this.testArr = this.testArr + 1
}
}
};
</script>
學習目標
-
watch的兩種用法
- 通過組件的參數(shù),watch作為對象
- 通過 vm.$watch() 方法來調用
-
避免死循環(huán)
- 比如 watch: { count: {this.count = this.count + 1}}
- 上面會觀測count的變化,變化后修改count,count變化又繼續(xù)調用cb去修改count,死循環(huán)了
-
wath對象的key對象的value的類型
- function
- object
- array
- string
- 最終都會把不同類型的 handler 轉換成函數(shù)
-
watch對象的 options 對象支持的屬性
- deep
- 深度監(jiān)聽
- 循環(huán) ( 訪問 ) watch對象中的key對應的 vm.key 嵌套對象的每一個屬性,從而觸發(fā)依賴數(shù)據(jù)的響應式get,通過 dep.depend()
- 向 user watcher 的 newDeps 中添加 dep
- 向 dep 的 subs 中添加 user watcher
- immediate
- 立即執(zhí)行cb,即wache對象中的 handler 函數(shù),無需等到依賴變化才去執(zhí)行
- 直接調用 cb(watcher.value)
- sync
- 保證 ( 同步wath對象的handler ) 在 ( 普通的watch對象的handler ) 前面執(zhí)行
- sync 就直接調用 watcher.run() => this.cb.call(this.vm, value, oldValue) 從而直接執(zhí)行cb函數(shù)
- deep
-
watch初始化的流程
- 處理watche對象key對應的value的各種類型,把object,array,string都處理成對象的function
- 執(zhí)行 vm.$watchg
- new userWatcher()
- constructor中通過this.get()調用getter函數(shù),把watch對象中的key通過 this.getter = parsePath(expOrFn) 方法分割成數(shù)組,通過 vm[key] 去訪問,返回watch對象中key對應的響應式數(shù)據(jù)
- 訪問的時候,又會觸發(fā)響應式數(shù)據(jù)的get方法,從而進行依賴收集,在dep中收集user watcher,用于更新
-
更新流程
- 依賴變化,觸發(fā)dep.notify(),玄幻dep.subs數(shù)據(jù)中的watcher.update()去更新
- 如果 sync=true就直接調用watcher.run => this.cb.call(this.vm, value, oldValue)
- 如果 sync=false, queueWatcher => nextTick(flushSchedulerQueue) => watcher.run() => this.cb.call(this.vm, value, oldValue)
- 依賴變化,觸發(fā)dep.notify(),玄幻dep.subs數(shù)據(jù)中的watcher.update()去更新
watch 源碼
-
Vue.prototype._init=>initState=>initWatch(vm, opts.watch)=>createWatcher(vm, key, handler)=>vm.$watch(expOrFn, handler, options)
- initWatch - src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
// initWatch(vm, opts.watch)
for (const key in watch) {
const handler = watch[key]
// handler
// watch對象中的 key 對應的 value
// 可能是 函數(shù),數(shù)組,對象,字符串(方法名)
if (Array.isArray(handler)) {
// handler是數(shù)組,就遍歷,把每一個成員傳入 createWatcher
// 成員一般是函數(shù)
// 比如
// watch: {
// testArr: [
// function handler1() {
// console.log(1111);
// },
// function handler2() {
// console.log(2222);
// }
// ]
// }
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
// handler是對象,函數(shù),字符串
// 比如
// watch: {
// count: function(val, newVal) {
// console.log(val, newVal);
// },
// count1: {
// handler(v, oldv) {
// console.log(v, oldv, "immediate立即執(zhí)行,不需要依賴變化", "后執(zhí)行");
// },
// immediate: true,
// deep: true,
// sync: true,
// },
// testHandlerIsFunctionName: 'watchHandlerIsFnName'
// }
createWatcher(vm, key, handler)
}
}
}
- createWatcher - src/core/instance/state.js
function createWatcher (
vm: Component,
expOrFn: string | Function, // watch對象中的 key
handler: any, // watch對象中的key對應的value => 對象,函數(shù),數(shù)組成員,字符串
options?: Object // 初始化時是 undefined
) {
if (isPlainObject(handler)) {
// handler 是一個對象
// 比如
// count1: {
// handler(v, oldv) {
// console.log(v, oldv);
// },
// immediate: true,
// deep: true,
// sync: true
// }
options = handler
handler = handler.handler
// handler是對象,就把handler賦值給options,把handler對象的handler方法賦值給handler變量
// 其實就是處理參數(shù)
}
if (typeof handler === 'string') {
handler = vm[handler]
// handler 是一個字符串,就賦值這個字符串代表的方法,在methods對象中定義的方法
}
return vm.$watch(expOrFn, handler, options)
// 傳入 vm.$watch 的 handler 都已經(jīng)處理成了 函數(shù)
}
- Vue.prototype.$watch - src/core/instance/state.js
Vue.prototype.$watch = function (
expOrFn: string | Function,
// expOrFn
// watch對象的key
cb: any,
// cb
// cb是watcher對象中key對應的value各種情況轉換之后的handler函數(shù)(可能值是函數(shù),數(shù)組,對象,字符串,到這里都轉成了函數(shù))
// 如果不是通過watch對象傳入new Vue()的方式,而是直接通過vm.$watch傳入,則cb就還可能是 (函數(shù),對象,數(shù)組,字符串)
options?: Object
// options 是配置對象
// options的屬性可能是下面幾個
// handler immediate deep sync 等
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
// 這里又判斷了 cb 是不是對象,原因如下
// 1. 因為 $watch 可以通過 vm.$watch 的方式調用
// 2. 如果是通過傳入 new Vue({}) 以 watch 對象的方法, cd就是已經(jīng)是經(jīng)過處理過后的 函數(shù)了,不用再判斷對象的情況
return createWatcher(vm, expOrFn, cb, options)
// 所以如果是上面 1 的情況,就又會調用 createWatcher()去處理handler的類型,處理成函數(shù)
}
options = options || {}
options.user = true
// 向 options 對象添加 user 屬性,值為true
const watcher = new Watcher(vm, expOrFn, cb, options)
// new 一個 user watcher
if (options.immediate) {
// immediate 屬性存在,就立即執(zhí)行 cb,即 handler函數(shù)
try {
cb.call(vm, watcher.value)
// cb 即 handler 函數(shù),是接收兩個參數(shù)的,這里只傳了第一個參數(shù),所以答應的話第二個參數(shù)是 undefined
// 第一個參數(shù) newValue
// 第二個參數(shù) oldValue
// watch: {
// count: function(val, newVal) {
// console.log(val, newVal);
// }
// }
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
// Vue.prototype.$watch 函數(shù),會返回 unwatchFn 函數(shù)
watcher.teardown()
// watcher.teardown()
// 1. 刪除_watchers中的 user watcher
// 2. 刪除 user watcher 中的 deps 中的所有 dep
// teardown () {
// if (this.active) {
// // this.active = true 默認為true
// if (!this.vm._isBeingDestroyed) {
// remove(this.vm._watchers, this)
// // 移除 watchers 數(shù)組中的 watcher
// }
// let i = this.deps.length
// while (i--) {
// this.deps[i].removeSub(this)
// // 同時刪除 watcher 的 deps 中的所有 watcher
// // 比如 在 user watcher,$watch方法最后就會刪除 user watcher 的 deps 中訂閱的 dep
// }
// this.active = false
// // this.active = false
// }
// }
}
}
- watcher - scr/core/observer/watcher.js
export default class Watcher {
vm: Component;
expression: string;
cb: Function; // 比如user watcher 中的 handler 函數(shù)
id: number;
deep: boolean;
user: boolean;
lazy: boolean; // computed watcher 的標志
sync: boolean; // user watcher 的 options對象中的 sync 屬性
dirty: boolean; // 用于 computed watcher
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 // user watcher 的 options.user 默認為true
this.lazy = !!options.lazy // computed watcher 的 options.lazy 默認為true
this.sync = !!options.sync // 用于 user watcher
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 {
// expOrFn 不是一個函數(shù)
// 因為:user watcher 中的 expOrFn就是watch對象中的key, 就是一個字符串
// 所以:用 parsePath 函數(shù)就行操作
this.getter = parsePath(expOrFn)
// this.getter
// 1. parsePath(expOrFn)
// 返回一個函數(shù),返回函數(shù)的參數(shù)是 vm 實例
// return function (obj) {
// // 1. path => 比如 expOrFn = path = 'a.b'
// // 2. ojb => vm
// // 上面 1和2,那么下面的循環(huán):
// // vm.a => 訪問到了a
// // vm.a.b => 訪問到了b
// for (let i = 0; i < segments.length; i++) {
// if (!obj) return
// obj = obj[segments[i]]
// }
// return obj
// }
// 2. this.getter是在 watcher.get()中調用的
// this.getter.call(vm, vm)
// 所以:1中返回函數(shù)的參數(shù)是 vm
// export function parsePath (path: string): any {
// if (bailRE.test(path)) {
// return
// }
// const segments = path.split('.')
// // segments 可能情況
// // 1.'a.b' 即觀測 a對象的b屬性 => [a, b]
// // 2. a => [a]
// return function (obj) {
// // 1. path => 比如 expOrFn = path = 'a.b'
// // 2. ojb => vm
// // 上面 1和2,那么下面的循環(huán):
// // vm.a => 訪問到了a
// // vm.a.b => 訪問到了b
// for (let i = 0; i < segments.length; i++) {
// if (!obj) return
// obj = obj[segments[i]]
// }
// return obj
// // 返回 響應式get函數(shù)中返回的值
// }
// }
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()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// 比如 user watcher 的options中配置了 sync:true 時,調用run方法
this.run()
} else {
queueWatcher(this)
// export function queueWatcher (watcher: Watcher) {
// const id = watcher.id
// if (has[id] == null) {
// has[id] = true
// if (!flushing) {
// queue.push(watcher)
// } else {
// // if already flushing, splice the watcher based on its id
// // if already past its id, it will be run next immediately.
// let i = queue.length - 1
// while (i > index && queue[i].id > watcher.id) {
// i--
// }
// queue.splice(i + 1, 0, watcher)
// }
// // queue the flush
// if (!waiting) {
// waiting = true
// if (process.env.NODE_ENV !== 'production' && !config.async) {
// flushSchedulerQueue()
// return
// }
// nextTick(flushSchedulerQueue)
// }
// }
// }
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
// 如果是 user watcher
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown () {
if (this.active) {
// this.active = true 默認為true
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
// 移除 watchers 數(shù)組中的 watcher
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
// 同時刪除 watcher 的 deps 中的所有 watcher
// 比如 在 user watcher,$watch方法最后就會刪除 user watcher 的 deps 中訂閱的 dep
}
this.active = false
// this.active = false
}
}
}
- parsePath - src/core/util/lang.js
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
// segments 可能情況
// 1.'a.b' 即觀測 a對象的b屬性 => [a, b]
// 2. a => [a]
return function (obj) {
// 1
// 1. path => 比如 expOrFn = path = 'a.b'
// 2. ojb => vm
// 上面 1和2,那么下面的循環(huán):
// vm.a => 訪問到了a
// vm.a.b => 訪問到了b
// 2
// 1. path => 比如 expOrFn = path = 'a'
// 2. ojb => vm
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
// 1. 最終會返回 vm.a.b
// 2. 最終返回 vm.a
}
}