导航
[[深刻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 } }
发表回复