深入浅出vm.$watch和watch初始化
2020/3/20 11:01:52
本文主要是介绍深入浅出vm.$watch和watch初始化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录
1.业务场景
2.用法
3.vm.$watch内部原理
4.deep参数实现原理
5.初始化watch
6.总结
1.业务场景
watch是用来监听某个数据发生变化,之后调用什么函数处理。一个数据可以去影响多个数据,比如说浏览器自适应、监控路由对象、监控自身属性变化等等。
2.用法
vm.$watch( expOrFn, callback, [options] )
定义:官方文档是这么写的:观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
实例:
// 键路径 vm.$watch('a.b.c', function (newVal, oldVal) { // 做点什么 }) // 函数 vm.$watch( function () { // 表达式 `this.a + this.b` 每次得出一个不同的结果时 // 处理函数都会被调用。 // 这就像监听一个未被定义的计算属性 return this.a + this.b }, function (newVal, oldVal) { // 做点什么 } ) 复制代码
选项deep:为了发现对象内部值的变化,可以在选项参数中指定 deep: true 。注意监听数组的变动不需要这么做。
vm.$watch('someObject', callback, { deep: true }) vm.someObject.nestedValue = 123 // callback is fired 复制代码
选项immediate:在选项参数中指定 immediate: true将立即以表达式的当前值触发回调。
vm.$watch('a', callback, { immediate: true }) // 立即以 `a` 的当前值触发回调 复制代码
返回值:vm.$watch会返回一个取消此监听的函数。
var unwatch = vm.$watch( 'value', function () { doSomething() if (unwatch) { unwatch() } }, { immediate: true } ) 复制代码
3.vm.$watch内部原理
实际上,vm.$watch其实是对watcher的一种封装,watcher是什么,不太懂的可以参考我这篇文章:深入浅出vue变化侦测。
我们通过watcher完全可以实现watcher的功能,但同时,vm.$watch中的参数deep和immediate是没有的。下面我们看看vm.$watch是如何实现的:
Vue.prototype.$watch=function(exp,cb,options){ const vm=this options=options||{} const watcher=new Watcher(vm,exp,cb,options) if(options.immediate){ cb.call(vm,watcher.value) } return function unwatchFn(){ watcher.teardown() } } 复制代码
逻辑也很简单,执行 new Watcher来实现vm.$watch基本功能的。
但是有一个细节,exp是可以接受函数的,所以watcher构造函数就要改一改。
export default class watcher{ constructor(vm,exp,cb){ this.vm=vm //exp参数支持函数 if(typeof exp==='function'){ this.getter=exp }else{ this.getter=parsePath(exp) } this.cb=cb this.value=this.get() } ...... } 复制代码
当exp为函数时,它不止可以动态返回数据,其中读取的所有数据都会被watcher观察到。任何一个发生变化时,wacther都会得到通知。
返回值:执行完vm.$watch后,返回一个函数unwatchFn,作用取消观察数据。既然要取消观察,我们就要知道watcher它自己都订阅了谁,也就是说watcher实例收集了哪些Dep。循环自己收集的Dep将自己从Dep中的依赖移除即可。
在watcher中添加addDep方法,作用是记录自己都订阅了哪些Dep:
export default class watcher{ constructor(vm,exp,cb){ this.vm=vm //新增 this.deps=[] // 新增 this.depIds=new Set() if(typeof exp==='function'){ this.getter=exp }else{ this.getter=parsePath(exp) } this.cb=cb this.value=this.get() } //新增 addDep(dep){ const id=dep.id // dep watcher产生互相关联 if(!this.depIds.has[id]){ this.depIds.add(id) //watcher添加dep this.deps.push(dep) //dep添加watcher dep.addSub(this) } } ...... } 复制代码
代码分析:我们使用了depIds来判断当前watcher已经订阅Dep,就不会重新订阅。当watcher读取value时,会触发收集依赖逻辑。通过this.depIds.add(id)来记录watcher已经订阅了这个Dep,通过this.deps.push(dep)记录自己都订阅了哪些Dep,最后触发dep.addSub(this)将自己添加到Dep中。
watcher中新增了addDep后,Dep中收集依赖的逻辑也需要改变:
let uid=0 //新增 export default class Dep{ constructor(){ this.id=uid++ //新增 this.subs=[] } depend(){ if(window.target){ this.addSub(window.target) //废弃 window.target.addDep(this) //新增 } } } 复制代码
代码分析:此时,Dep会记录数据发生变化时,需要通知哪些watcher,而watcher也记录了自己被哪些Dep通知。所以Dep和Watcher是多对多的关系
为什么是多对多的关系:我们知道当视图多次使用一个数据时,此时一个Dep对应多个watcher.同时,当一个watcher观察的参数是函数时,此时该函数使用了多个数据时,那么这个watcher就要收集多个Dep了。
unwatcher:我们已经知道watcher订阅了哪些Dep,就可以在Watcher中新增teardown方法来通知这些订阅,把他们从依赖列表中移除掉:
//从dep列表移除自己 teardown(){ let i=this.deps.length while(i--){ this.deps[i].removeSub(this) } } 复制代码
dep中添加removeSub方法:
removeSub(sub){ const index=this.subs.indexOf(sub) if(index>-1){ return this.subs.splice(index,1) } } 复制代码
以上就是unwatch的原理,当数据改变时,也不会通知已经删去的watcher了。
4deep参数原理实现
deep的功能就是除了要触发当前这个被监听数据的收集依赖逻辑之外,还要把这个值在内的所有子值都要触发一遍收集依赖,watcher更改如下:
export default class watcher{ constructor(vm,exp,cb){ this.vm=vm //新增 if(options){ this.deep=!!options.deep }else{ this.deep=false } this.deps=[] this.depIds=new Set() if(typeof exp==='function'){ this.getter=exp }else{ this.getter=parsePath(exp) } this.cb=cb this.value=this.get() } get(){ window.target=this let value=this.getter.call(this.vm,this.vm) //新增 if(this.deep){ traverse(value) } window.target=undefined return value } } 复制代码
代码分析:如果用户使用了deep参数,会在window.target=undefined之前调用traverse来处理deep逻辑。否则,watcher就不会收集到子值的依赖列表中了。
traverse函数:其实这个函数很简单。就是采用递归的方式,要递归value所有子值来触发他们收集依赖功能。
5.初始化watch
类型:{ [key: string]: string | Function | Object | Array }
官网解释:一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。
示例:
var vm = new Vue({ data: { a: 1, b: 2, c: 3, d: 4, e: { f: { g: 5 } } }, watch: { a: function (val, oldVal) { console.log('new: %s, old: %s', val, oldVal) }, // 方法名 b: 'someMethod', // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深 c: { handler: function (val, oldVal) { /* ... */ }, deep: true }, // 该回调将会在侦听开始之后被立即调用 d: { handler: 'someMethod', immediate: true }, e: [ 'handle1', function handle2 (val, oldVal) { /* ... */ }, { handler: function handle3 (val, oldVal) { /* ... */ }, /* ... */ } ], // watch vm.e.f's value: {g: 5} 'e.f': function (val, oldVal) { /* ... */ } } }) vm.a = 2 // => new: 2, old: 1 复制代码
初始化分析: 初始化watch其实不难,watch和vm.$watch功能是相同的,所以只要循环watch选项,将对象每一项调用vm.$watch方法来实现。
但是watch选项的值同时支持字符串,函数,数组,对象。不同的类型有不同的用法,所以在调用vm.$watch时我们需要做一些适配。
function initWatch(vm,watch){ for(const key in watch){ const handler=watch[key] if(Array.isArray(handler)){ for(let i=0;i<handler.length;i++){ createWatcher(vm,key,handler[i]) } }else{ createWatcher(vm,key,handler) } } } 复制代码
代码分析:先把watch选项值分为两类,数组和其他。接着在调用createWatcher函数处理其他类型并调用vm.$watch创建观察表达式。
function createWatcher(vm,exp,handler,options){ if(isPlainObject(handler)){ options=handler handler=handler.handler } if(typeof handler==='string'){ handler=vm[handler] } return vm.$watch(exp,handler,options) } 复制代码
代码分析:处理了三种类型:
- 函数:不用特殊处理,直接传递给vm.$watch
- 字符串:从vm取出方法,将它赋值给handler
- 对象:options的值设置为handler,并且将变量handler设置为handler对象handler方法。
watch初始化原理就已经说完了。
6.总结
关于watch这一方面其实不是很难,主要是灵活运用了Watcher类和Dep类.
一天较真一个api,一天比一天进步。
这篇关于深入浅出vm.$watch和watch初始化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04el-table 开启定时器下,表格的选中状态会消失是什么原因-icode9专业技术文章分享
- 2024-10-03如何安装和初始化飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03如何安装 App 并连接到飞牛 NAS?-icode9专业技术文章分享
- 2024-10-03如何安装飞牛 TV 并连接到影视服务器?-icode9专业技术文章分享
- 2024-10-03如何在PVE和ESXI上安装飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS安装系统异常情况处理-icode9专业技术文章分享
- 2024-10-03飞牛NAS如何创建存储空间?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS硬盘会自动休眠吗?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何安装飞牛影视和创建媒体库?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何为家人朋友开通影视账号?-icode9专业技术文章分享