Vue源码之用户watcher
Posted 天地会珠海分舵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue源码之用户watcher相关的知识,希望对你有一定的参考价值。
通过上一篇文章《Vue源码之渲染watcher》我们学习到了每个组件实例初始化时都会创建一个渲染watcher来监控页面引用到的响应式数据的变动,一旦数据发生变化,就会通知渲染watcher来重新生成虚拟DOM,做diff,update,然后patch来更新页面。
今天我们将会学习下vue框架用到的第三个watcher - 用户watcher。下面回忆下这三个watcher的简介:
- 计算属性watcher: vue会为我们在computed选项写的每个我们自定义的计算属性创建一个计算属性watcher,该watcher会在依赖的响应式数据变化时将计算属性标志位设置成dirty,使得页面在下次更新时调用计算属性函数进行求值。
- 渲染watcher: vue会为每个组件创建一个渲染watcher来在依赖的响应式数据状态发生变化时重新渲染页面
- 用户watcher: vue会为我们在watch选项写的每个要监控的属性创建一个watcher来在其变化时执行提供的回调函数
既然用户watcher是根据我们编写的watch选项而创建的,所以我们下面先简单介绍下watch的用法。
1. watch用法简介
我们在编写组件代码时,如果我们需要监控某个响应式属性的变化,我们会在watch选项下面实现相关的监听函数。
标准的写法应该是下面这个样子的
watch:
counter:
handler: function (newVal, oldVal)
console.log(newVal, oldVal);
,
,
,
如果不需要提供immediate,deep等选项的话,可以简化写成下面这样
watch:
counter: function (newVal, oldVal)
console.log(newVal, oldVal);
,
,
一个完整的vue实例初始化例子如下
<!DOCTYPE html>
<html lang="en">
<head>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<div>counter</div>
<button @click="increase">Increase</button>
</div>
<script>
const vm = new Vue(
el: "#app",
data:
counter: 1,
,
methods:
increase()
this.counter += 1;
,
,
watch:
counter:
handler: function (newVal, oldVal)
console.log(newVal, oldVal);
,
,
,
);
</script>
</body>
</html>
2. 用户watcher源码分析
下面开始分析下我们写的watch选项是怎么生成用户watcher的。
我们编写vue实例或者组件实例的时候,比如上面的创建vue实例中的例子,在写好watch选项之后,vue会将这些选项放到options参数并传递给他_init方法来对实例进行初始化。然后会经历一系列的初始化函数调用流程。
// core/instance/index.js文件
function Vue(options)
this._init(options);
// core/instances/init.js文件
Vue.prototype._init = function (options?: Object)
const vm: Component = this;
...
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || ,
vm)
...
initState(vm);
// core/instance/sate.js文件
export function initState(vm: Component)
vm._watchers = []
const opts = vm.$options
....
if (opts.watch && opts.watch !== nativeWatch)
initWatch(vm, opts.watch)
在_init方法中会对我们传入的选项配置进行处理,然后放入到实例的$options选项中。到了initState时,就会以我们编写的watch配置选项作为参数调用initWatch方法对用户watcher进行初始化。
function initWatch(vm: Component, watch: Object)
for (const key in watch)
const handler = watch[key]
...
createWatcher(vm, key, handler)
这里会直接调用createWatcher方法
function createWatcher(
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
)
if (isPlainObject(handler))
options = handler
handler = handler.handler
...
return vm.$watch(expOrFn, handler, options)
这里留意下handler处理部分,如果我们的watch的回调函数写法是标准带handler的那种写法的话,需要将配置项里面的handler拿出来作为本函数里面的handler函数。
跟着就是调用vue的原型函数$watch方法
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function
const vm: Component = this
...
options = options ||
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
...
这里注意options.user = true,这就是我们这个要创建的watcher叫做用户watcher的原因。
在调用Watcher构造函数的时候,我们这次提供了四个参数,值得留意的是,相比上两篇文章分析的渲染watcher和计算属性watcher,我们这次提供了第三个参数,即回调函数,该函数将会在依赖的响应式属性变化时被watcher执行。而这个cb,就是我们上面的handler,也就是我们自己写的那个watch的handler函数。
下面看下用户watche的构造过程
export default class Watcher
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
)
this.vm = vm;
...
// options
if (options)
...
this.user = !!options.user;
this.cb = cb;
...
if (typeof expOrFn === "function")
this.getter = expOrFn;
else
this.getter = parsePath(expOrFn);
...
this.value = this.lazy ? undefined : this.get();
Watcher的构造函数其实我们看了很多遍了,只是为了方便分析,针对不同使用情况下的watcher,我们就会把和它不相关的代码给省略掉。这里也一样,我们这里只保留和今天学习用户Watcher相关的核心代码。
- 首先,这里把用户watcher的标识位存储到user成员变量中
- 然后,将我们编写的handler函数保存到cb成员变量中
- 跟着,因为我们提供的expOrFn是’counter’或者’counter.total’这样的字符串,所以会调用parsePath来返回一个getter函数,该函数会接受一个对象,然后把对象里面的expOrFn为key的对象返回,比如接收vm对象,然后将下面的counter属性的的值给返回。该函数我们在《Vue源码分析基础之响应式原理》的4.3章节中已经做过详细的分析,这里就不再赘述。
- 跟着,调用get成员方法去进行依赖收集
下面我们将再次看下get成员方法的代码
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;
这个方法我们此前的文章也分析过很多次了,目的就是调用getter,即读取下我们监控的响应式属性,在我们的示例中,就是读取一下vm中的count,从而触发其getter,进而将我们的用户watcher加入到该响应式属性的依赖deb.subs中,即完成依赖收集过程。
如此一来,在下次有人修改了该响应式数据之后,将会遍历该响应式数据的所有订阅者,即所有依赖的watcher,包含这里的用户watcher,然后通知这些watcher去做事情,或者是像计算属性watcher那样去更新数据,或者像渲染watcher那样去重新计算虚拟DOM然后更新页面,或者是我们这里的用户watcher,则会重新执行下我们自己编写的handler回调。
以上,就是用户watcher的源码的简单分析。而这一系列的三个不同用途的watcher的源码分析也就告一段落了。多谢大家的观看和支持吧。
以上是关于Vue源码之用户watcher的主要内容,如果未能解决你的问题,请参考以下文章