导航
[[深刻 01] 执行上下文 ](https://juejin.im/post/684490…)
[[深刻 02] 原型链 ](https://juejin.im/post/684490…)
[[深刻 03] 继承 ](https://juejin.im/post/684490…)
[[深刻 04] 事件循环 ](https://juejin.im/post/684490…)
[[深刻 05] 柯里化 偏函数 函数记忆 ](https://juejin.im/post/684490…)
[[深刻 06] 隐式转换 和 运算符 ](https://juejin.im/post/684490…)
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…)
[[深刻 08] 前端平安 ](https://juejin.im/post/684490…)
[[深刻 09] 深浅拷贝 ](https://juejin.im/post/684490…)
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…)
[[深刻 11] 前端路由 ](https://juejin.im/post/684490…)
[[深刻 12] 前端模块化 ](https://juejin.im/post/684490…)
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定 ](https://juejin.im/post/684490…)
[[深刻 14] canvas](https://juejin.im/post/684490…)
[[深刻 15] webSocket](https://juejin.im/post/684490…)
[[深刻 16] webpack](https://juejin.im/post/684490…)
[[深刻 17] http 和 https](https://juejin.im/post/684490…)
[[深刻 18] CSS-interview](https://juejin.im/post/684490…)
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…)
[[深刻 20] 手写函数 ](https://juejin.im/post/684490…)
[[react] Hooks](https://juejin.im/post/684490…)
[[部署 01] Nginx](https://juejin.im/post/684490…)
[[部署 02] Docker 部署 vue 我的项目 ](https://juejin.im/post/684490…)
[[部署 03] gitlab-CI](https://juejin.im/post/684490…)
[[源码 -webpack01- 前置常识] AST 形象语法树 ](https://juejin.im/post/684490…)
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…)
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程 ](https://juejin.im/post/684490…)
[[源码] Redux React-Redux01](https://juejin.im/post/684490…)
[[源码] axios ](https://juejin.im/post/684490…)
[[源码] vuex ](https://juejin.im/post/684490…)
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…)
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…)
[[源码 -vue03] watch 侦听属性 – 初始化和更新 ](https://juejin.im/post/684490…)
[[源码 -vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490…)
[[源码 -vue05] Vue.extend ](https://juejin.im/post/684490…)
[[源码 -vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790…)
前置常识
一些单词
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 立刻执行: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"> 扭转所有数据 - 验证 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) { // ---------------------------------- 函数
console.log(val, newVal);
},
count1: {handler(v, oldv) {console.log(v, oldv, "immediate 立刻执行,不须要依赖变动", "后执行");
},
immediate: true
},
nestObj: { // ------------------------------------------------------ 对象
handler(v, oldv) {console.log(v.count2, oldv.count2, "sync 再 nextTick 之前先执行");
},
deep: true,
sync: true // 同步 先于 异步的 watch 执行,默认是异步
},
deepCopyNestObj(newVal, oldVal) {console.log(newVal.count2, oldVal.count2, 'deep 深度观测 - 遇到问题是新旧值一样,能够用 commputed 做深拷贝')
},
"nestObj.count3": function() {
// 监听对象中的某个属性,能够应用 obj.xxx 的字符串模式作为 key
console.log("watch 到了 nestObj.count3");
},
testArr: [ // ------------------------------------------------------ 数组
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 的两种用法
- 通过组件的参数,watch 作为对象
- 通过 vm.$watch() 办法来调用
-
防止死循环
- 比方 watch: {count: {this.count = this.count + 1}}
- 下面会观测 count 的变动,变动后批改 count,count 变动又持续调用 cb 去批改 count,死循环了
-
wath 对象的 key 对象的 value 的类型
- function
- object
- array
- string
- 最终都会把不同类型的 handler 转换成函数
-
watch 对象的 options 对象反对的属性
-
deep
- 深度监听
-
循环 (拜访) watch 对象中的 key 对应的 vm.key 嵌套对象的每一个属性,从而触发依赖数据的响应式 get,通过 dep.depend()
- 向 user watcher 的 newDeps 中增加 dep
- 向 dep 的 subs 中增加 user watcher
-
immediate
- 立刻执行 cb,即 wache 对象中的 handler 函数,无需等到依赖变动才去执行
- 间接调用 cb(watcher.value)
-
sync
- 保障 (同步 wath 对象的 handler) 在 (一般的 watch 对象的 handler) 后面执行
- sync 就间接调用 watcher.run() => this.cb.call(this.vm, value, oldValue) 从而间接执行 cb 函数
-
-
watch 初始化的流程
- 解决 watche 对象 key 对应的 value 的各种类型,把 object,array,string 都解决成对象的 function
- 执行 vm.$watchg
-
new userWatcher()
- constructor 中通过 this.get() 调用 getter 函数,把 watch 对象中的 key 通过 this.getter = parsePath(expOrFn) 办法宰割成数组,通过 vm[key] 去拜访,返回 watch 对象中 key 对应的响应式数据
- 拜访的时候,又会触发响应式数据的 get 办法,从而进行依赖收集,在 dep 中收集 user watcher,用于更新
-
更新流程
-
依赖变动,触发 dep.notify(),玄幻 dep.subs 数据中的 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)
-
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 // 可能是 函数,数组,对象,字符串 (办法名) if (Array.isArray(handler)) { // handler 是数组,就遍历,把每一个成员传入 createWatcher // 成员个别是函数 // 比方 // 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 是对象,函数,字符串 // 比方 // watch: {// count: function(val, newVal) {// console.log(val, newVal); // }, // count1: {// handler(v, oldv) {// console.log(v, oldv, "immediate 立刻执行,不须要依赖变动", "后执行"); // }, // 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 => 对象,函数,数组成员,字符串 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 变量 // 其实就是解决参数 } if (typeof handler === 'string') {handler = vm[handler] // handler 是一个字符串,就赋值这个字符串代表的办法,在 methods 对象中定义的办法 } return vm.$watch(expOrFn, handler, options) // 传入 vm.$watch 的 handler 都曾经解决成了 函数 }
-
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 函数 (可能值是函数,数组,对象,字符串,到这里都转成了函数) // 如果不是通过 watch 对象传入 new Vue() 的形式,而是间接通过 vm.$watch 传入,则 cb 就还可能是 ( 函数,对象,数组,字符串) 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 就是曾经是通过解决过后的 函数了,不必再判断对象的状况 return createWatcher(vm, expOrFn, cb, options) // 所以如果是下面 1 的状况,就又会调用 createWatcher()去解决 handler 的类型,解决成函数 } options = options || {} options.user = true // 向 options 对象增加 user 属性,值为 true const watcher = new Watcher(vm, expOrFn, cb, options) // new 一个 user watcher if (options.immediate) { // immediate 属性存在,就立刻执行 cb,即 handler 函数 try {cb.call(vm, watcher.value) // cb 即 handler 函数,是接管两个参数的,这里只传了第一个参数,所以许可的话第二个参数是 undefined // 第一个参数 newValue // 第二个参数 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 函数,会返回 unwatchFn 函数 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 数组中的 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 函数 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 不是一个函数 // 因为:user watcher 中的 expOrFn 就是 watch 对象中的 key, 就是一个字符串 // 所以:用 parsePath 函数就行操作 this.getter = parsePath(expOrFn) // this.getter // 1. parsePath(expOrFn) // 返回一个函数,返回函数的参数是 vm 实例 // return function (obj) { // // 1. path => 比方 expOrFn = path = 'a.b' // // 2. ojb => vm // // 下面 1 和 2,那么上面的循环:// // 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 中返回函数的参数是 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,那么上面的循环:// // 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 函数中返回的值 // } // } 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 数组中的 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,那么上面的循环:// 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 } }