主题
侦听器watch
侦听器的使用形式
- 函数
js
watch: {
// 侦听器name是一个函数
name(val, oldVal) {
// do something
}
}
- 数组函数
js
watch: {
// 侦听器name是一个函数数组
name: [
function (val, oldval) {
/* .... */
},
function (val, oldval) {
/* .... */
}
];
}
- 函数对象
js
watch: {
name: [
{
handler(val, oldval) {
/* ... */
},
deep: true,
immediate: true
}
// {...}
];
}
- 字符串
js
watch: {
name: 'computedNamePath'
},
methods: {
computedNamePath(val, oldVal) {/* .... */}
}
- 对象
js
watch: {
name: {
handler(val, oldval) {/* ... */}
deep: true,
immediate: true
}
}
initWatch
前面在initState
里面已经定义好了 Vue 对数据拦截的时候的顺序, props -> methods -> data -> computed -> watch, 前面已经写了initData
, 现在来实现initWatch
js
// state.js
function initWatch(vm) {
const watch = vm.$options.watch;
for (const key in watch) {
if (Array.isArray(watch[key])) {
for (let i = 0; i < watch[key].length; i++) {
const handler = watch[key][i];
createWatch(vm, key, handler);
}
} else {
createWatch(vm, key, watch[key]);
}
}
}
createWatch
js
function createWatch(vm, key, handler, opt) {
if (typeof handler === "object") {
opt = handler;
handler = handler.handler;
}
if (typeof handler === "string") {
handler = vm[handler];
}
return vm.$watch(key, handler, opt);
}
vm.$watch
$watch
是实例方法, 因此还需要通过 mixinXXX 来进行拓展
js
// state.js
// 与状态相关的Mixin
export function stateMixin(Vue) {
// 拓展$watch
Vue.prototype.$watch = function (key, handler, opt) {
const vm = this;
const options = {};
options.user = true;
if (!opt) {
opt = {};
}
new Watcher(vm, key, handler, { ...options, ...opt });
};
}
到这里, watch
侦听器的原理已经很明显, 就是一个watcher
, Watcher
也有了第二个角色, 侦听器watcher
, 侦听器watcher
有一个特点, 就是user
为true
侦听器 watcher 依赖收集
先看 watch 里面的逻辑
js
let uid = 0;
class Watcher {
constructor(vm, exprFn, cb, options) {
this.vm = vm;
this.cb = cb; // 回调函数
// this.exprFn = exprFn; // 组件真是的渲染函数
this.uid = uid++; // 每一个组件都有一个渲染watcher, 这是唯一标识
this.depsIds = new Set(); // 一个watcher上面有多个dep, 且不重复
this.deps = [];
this.user = !!options.user;
this.options = !!options; // 是否为渲染watcher
if (this.user) {
// 若是watch, 则将watch的handler做为watcher的cb, 在依赖更新的时候调用这个cb
this.getter = function () {
let obj = vm;
const path = exprFn.split(".");
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]];
}
return obj;
};
} else {
this.getter = exprFn;
}
this.value = this.get(); // 首先就让组件渲染函数执行
}
run() {
const newVal = this.get();
if (newVal !== this.value) { // 对比老值与新值
this.value = newVal;
this.cb.call(this.vm, newVal, this.value); // 调用watch的handler
}
}
/* .....省略一些方法..... */
get() {
pushTarget(this);
let val = this.getter();
popTarget();
return val;
}
}
export default Watcher;
Watcher
内部逻辑进行了一定的改写, new Watcher
的时候,会先判断是否为watch
, 即user
是否为true
, 将watch
的建取值的操作封装为一个待执行的函数, 然后重新给getter
赋值为watch
的key
取值的操作的函数, 并执行这个函数, 得到最新的值, 存到watcher.value
上
接着调用这个取值函数, 取值会触发Object.defineProperty
的get
方法, 从而收集了依赖, 这部分代码是服用了初始化data
时候的依赖收集
当值被更改的时候, 会触发set
函数, 最终触发watcher.run()
, 在run
中可以拿到最新的值, 与老值进行比较, 若是不一样则需要调用cb