深入理解vue的watch

vue中的wactch可以监听到data的变化,执行定义的回调,在某些场景是很有用的,本文将深入源码揭开watch额面纱

前言

  • version: v2.5.17-beta.0
  • 阅读本文需读懂vue数据驱动部分

watch的使用

当改变data值,同时会引发副作用时,可以用watch。比如:有一个页面有三个用户行为会触发this.count的改变,当this.count改变,需要重新获取list值,这时候就可以用watch轻松完成

new Vue({
el: '#app',
data: {
count: 1,
list: []
},
watch: {
// 不管多少地方改变count,都会执行到这里去改变list的值
count(val) {
ajax(val).then(list => {
this.list = list;
})
}
},
methods: {
// 点击+1,count + 1,刷新列表
handleClick() {
this.count += 1;
},
// 点击重置,count = 1,刷新列表
handleReset() {
this.count = 1;
},
// 点击随机, count随机数,刷新列表
handleRamdon() {
this.count = Math.ceil(Math.random() * 10);
}
}
})

这样的好处就是把所有源头聚集到了watch中,不需要在多个count改变的地方手动去调用方法,减少代码冗余。

watch的多种使用方式

watch的写法有多种,以上案例是最常见的一种方法,接下来介绍所有写法。

传值函数

new Vue({
data: {
count: 1
},
watch: {
count() {
console.log('count改变')
}
}
})

最常见的写法,count改变时将会触发传值的回调函数

传值数组

new Vue({
data: {
count: 1
},
watch: {
count: [
() => {
console.log('count改变')
},
() => {
console.log('count watch2')
}
]
}
})

传数组,count改变后会依次执行数组内每一个回调函数

传值字符串

new Vue({
data: {
count: 1
},
watch: {
count: 'handleChange'
},
methods: {
handleChange(val) {
console.log('count改变了')
}
}
})

我们也可以传值字符串handleChange,然后在methods写handleChange函数的逻辑,同样可以做到count改变执行handleChange

传值对象

new Vue({
data: {
count: 1
},
watch: {
count: {
handler() {
console.log('count改变')
}
}
}
})

可以传值对象,该对象包含一个handler函数,当count改变时,会执行此handler函数,为什么多此一举需要包装一层对象呢?存在即合理,是有其特殊作用的。

传值对象的其他作用

watch为监听属性的变化,调用回调函数,因此,在初始化时,并不会触发,在初始化后属性改变才触发,如果想要初始时也要触发watch,那就需要传值对象,如下:

new Vue({
data: {
count: 1
},
watch: {
count: {
immediate: true, // 加此属性
handler() {
console.log('count改变')
}
}
}
})

传的对象有immediate属性为true,则watch会立刻触发。

源码分析watch

本节进行源码分析,探索watch的真面貌

初始watch

// 初始化
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.data) {
initData(vm);
}
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
// watch初始化
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
if (Array.isArray(handler)) {
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}

从vue的执行流程,读到了initWatch函数,此函数的用法很清晰,将传入的每一个watch属性执行createWatcher处理。如果传值是数组,则遍历去调用。

下面看一下createWatcher函数

function createWatcher (
vm,
expOrFn,
handler,
options
) {
// 如果是对象,则处理
if (isPlainObject(handler)) {
// 将对象缓存,给$watch函数
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}

createWatcher中做了兼容处理:

  1. 如果handler是个对象,则进行一步转换;
  2. 如果handler是字符串,则取vue实例的方法(methods里声明)
  3. 最后调用实例的$watch方法

创建Watcher


Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
cb.call(vm, watcher.value);
}
return function unwatchFn () {
watcher.teardown();
}
};

vm.$watch里是最终实现watch的部分,在这里仍然做了兼容判断,如果是对象,回调createWatcher;接下来就最重要的new Watcher。

$watch的功能其实就是new了一个Watcher,那么,我们在代码里实现的一切响应,都来自于Watcher,下面看一下watch里的Watcher

watchWatcher

Watcher是vue数据驱动核心部分的一员,他承载着依赖收集与事件的触发。下面重点解读一下watch的Watcher实现。

if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
// parsePath去解析expOrFn并返回getter函数
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = function () {};
}
}

watch Watcher会执行上面部分,parsePath源码可自行查看,他会将obj.a这种写法兼容, 最终是返回需要监听的属性的getter函数

if (this.computed) {
this.value = undefined;
this.dep = new Dep();
} else {
// 执行get方法
this.value = this.get();
}

拿到getter后,会执行this.get方法:

Watcher.prototype.get = function get () {
// 加入Dep.target
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm); // 执行
} catch (e) {
} finally {
popTarget();
this.cleanupDeps();
}
return value
};

以上为get方法,内容简单,但是做的事情举足轻重,他不仅做了值的获取,还做了依赖收集。

pushTarget会将当前watchWatcher赋值到Dep.target中,然后执行this.getter函数,要监听的属性如count会触发他的get钩子,与此同时会进行收集依赖,收集到的依赖就是前面Dep.target也就是当前的watchWatcher

正因为有上面的依赖收集,使count属性有了此watchWatcher的依赖,当this.count改变时,会触发set钩子,进行事件分发,从而执行回调函数

Watcher.prototype.getAndInvoke = function getAndInvoke (cb) {
var value = this.get();
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
this.dirty = false;
if (this.user) {
try {
cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
cb.call(this.vm, value, oldValue);
}
}
};

上面就是this.count改变时,最终调用的方法,在这里会执行this.cb,也就是定义的watch的回调函数,会把value/oldValue传递过去

立即执行的watch

前面说到,watch只会在监听的属性改变值后才会触发回调,在初始化时不会执行回调,如果想要一开始初始化就执行回调,需要传参对象,并immediate为true,实现原理已经在创建Watcher贴出来了

if (options.immediate) {
cb.call(vm, watcher.value);
}

创建watcher时,如果immediate为真值,会直接执行回调函数

与computed比较

computed是计算属性,watch是监听属性变化,有些场景计算属性做的事情,watch也可以做,当然要尽量用computed去做,为什么?

new Vue({
data: {
num: 1,
sum: 2
},
watch: {
num(val) {
this.sum = val + 1;
}
}
})

watch实现需要声明2个data属性num 和 sum,2个都会加入数据驱动,当num改变后,num和sum都触发了set钩子。

而computed不会,computed只会触发num的set钩子,因为sum根本没有声明,num改变后是动态计算出来的。

深入理解vue的watch的更多相关文章

  1. 理解vue中的scope的使用

    理解vue中的scope的使用 我们都知道vue slot插槽可以传递任何属性或html元素,但是在调用组件的页面中我们可以使用 template scope="props"来获取 ...

  2. 理解Vue中的Render渲染函数

    理解Vue中的Render渲染函数 VUE一般使用template来创建HTML,然后在有的时候,我们需要使用javascript来创建html,这时候我们需要使用render函数.比如如下我想要实现 ...

  3. 深入理解vue

    一 理解vue的核心理念 使用vue会让人感到身心愉悦,它同时具备angular和react的优点,轻量级,api简单,文档齐全,简单强大,麻雀虽小五脏俱全. 倘若用一句话来概括vue,那么我首先想到 ...

  4. 深入理解 Vue 组件

    深入理解 Vue 组件 组件使用中的细节点 使用 is 属性,解决组件使用中的bug问题 <!DOCTYPE html> <html lang="en"> ...

  5. vue系列---理解Vue中的computed,watch,methods的区别及源码实现(六)

    _ 阅读目录 一. 理解Vue中的computed用法 二:computed 和 methods的区别? 三:Vue中的watch的用法 四:computed的基本原理及源码实现 回到顶部 一. 理解 ...

  6. 手摸手带你理解Vue的Computed原理

    前言 computed 在 Vue 中是很常用的属性配置,它能够随着依赖属性的变化而变化,为我们带来很大便利.那么本文就来带大家全面理解 computed 的内部原理以及工作流程. 在这之前,希望你能 ...

  7. 手摸手带你理解Vue的Watch原理

    前言 watch 是由用户定义的数据监听,当监听的属性发生改变就会触发回调,这项配置在业务中是很常用.在面试时,也是必问知识点,一般会用作和 computed 进行比较. 那么本文就来带大家从源码理解 ...

  8. 不一样的角度理解Vue组件

    什么是组件 以Java.C#等面向对象编程语言的角度去理解Vue组件,能够发现组件和面向对象编程的方式和风格很相似.一切事物皆为对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽 ...

  9. 尝试用面向对象思维理解Vue组件

    什么是组件 用面向对象的思维去理解Vue组件,可以将所有的事物都抽象为对象,而类或者说是组件,都具有属性和操作. 如抽取人类为组件,其基本的属性有姓名.年龄.国籍:基本的方法有吃饭.睡觉.跑步等. & ...

随机推荐

  1. 如何在SpringMVC项目中部署WebService服务并打包生成客户端

    场景 某SpringMVC项目原本为一个HTTP的WEB服务项目,之后想在该项目中添加WebService支持,使该项目同时提供HTTP服务和WebService服务.其中WebService服务通过 ...

  2. Python工程编译成跨平台可执行文件(.pyc)

    原文:https://blog.csdn.net/zylove2010/article/details/79593655 在某些场景下,若不方便将python编写的源码工程直接给到其他人员,则可以将p ...

  3. [转]C#操作word模板插入文字、图片及表格详细步骤

    c#操作word模板插入文字.图片及表格 1.建立word模板文件 person.dot用书签 标示相关字段的填充位置 2.建立web应用程序 加入Microsoft.Office.Interop.W ...

  4. HDU 6709“Fishing Master”(贪心+优先级队列)

    传送门 •参考资料 [1]:2019CCPC网络选拔赛 H.Fishing Master(思维+贪心) •题意 池塘里有 n 条鱼,捕捉一条鱼需要花费固定的 k 时间: 你有一个锅,每次只能煮一条鱼, ...

  5. H3C 端口接入控制方式

  6. linux内核指针和错误值

    很多内部内核函数返回一个指针值给调用者. 许多这些函数也可能失败. 大部分情况, 失 败由返回一个 NULL 指针值来指示. 这个技术是能用的, 但是它不能通知问题的确切特性. 一些接口确实需要返回一 ...

  7. CDM命令实现MySql数据库文件的导出导入

    1.首先进入MySQL的安装目录,找到Bin文件夹,我这里安装的目录是C:\Program Files\MySQL\MySQL Server 8.0\bin ,进入该文件夹后在空白处按下Shift键+ ...

  8. centos7 创建sftp

    sftp是Secure File Transfer Protocol的缩写,安全文件传送协议.可以为传输文件提供一种安全的网络的加密方法.sftp 与 ftp 有着几乎一样的语法和功能.SFTP 为  ...

  9. python3中map函数

    map函数是Python里面比较重要的函数 map函数后面必须接的是序列(元组/列表/字符串) 在python2中执行一些语句 >>> map(str,[1,2,3,4]) ['1' ...

  10. Keras mlp 手写数字识别示例

    #基于mnist数据集的手写数字识别 #构造了三层全连接层组成的多层感知机,最后一层为输出层 #基于Keras 2.1.1 Tensorflow 1.4.0 代码: import keras from ...