赞
踩
目录
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
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,一天比一天进步。
关于找一找教程网
本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。
本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。
[深入浅出vm.$watch和watch初始化]http://www.zyiz.net/tech/detail-120079.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。