深入理解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. Java反射机制(二):通过反射取得类的结构

    在反射运用过程中,如果你想得到一个类的完整结构,那么就要使用到java.lang.reflect包中的几个类: · Constructor  表示类中的构造方法 · Field  表示类中的属性 · ...

  2. java Jre和Jdk的区别?

    JRE:(Java Runtime Environment),java运行环境.包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,如果想要运行一个开 ...

  3. win10系统激活 快捷方式

    系统不定期就会提示激活,每次激活都是找各种工具折腾,今天捣鼓简单的脚本直接激活~~ 首先查看自己系统的版本,后面才能找到合适的激活码 win+R 启动程序 输入 winver 即可查看系统版本 2.查 ...

  4. HttpServletRequest获得Url里面传来的值

    URL地址:http://XXXXX/manage/welcome?loginUser=123456String []str = request.getParameterValues("lo ...

  5. 北京信息科技大学第十一届程序设计竞赛E-- kotori和素因子(深搜)

    链接:https://ac.nowcoder.com/acm/contest/940/E 题目描述 kotori拿到了一些正整数.她决定从每个正整数取出一个素因子.但是,kotori有强迫症,她不允许 ...

  6. codeforces 1185G1 状压dp

    codeforces 1185G1. Playlist for Polycarp (easy version)(动态规划) 传送门:https://codeforces.com/contest/118 ...

  7. linux安装python3.*,更换Python2.*

    下载并解压:Python-3.5.7.tgz [root@AH-aQYWTYSJZX01 python3]# ll total 20268 -rw-r----- 1 temp01 temp01 207 ...

  8. SSI(服务器端嵌入)

    简介 SSI(服务器端嵌入)是一组放在 HTML 页面中的指令,当服务器向客户端访问提供这些页面时,会解释执行这些指令.它们能为已有的 HTML 页面添加动态生成内容,不需要通过 CGI 程序来或其他 ...

  9. Microsoft Edge 离线安装包下载

    Microsoft Edge 现已准备就绪 下一版 Microsoft Edge 已准备好进行企业评估. 立即下载离线安装程序.查看管理策略并尝试 Internet Explorer 模式. http ...

  10. Python3 安装pylint 及与PyCharm关联

    使用如下命令: pip3 install pylint 安装完后可以看到在你的python3的目录底下的Scripts目录下有pylint.exe了 然后就可以使用pylint 评估你的代码了,如: ...