浅谈 vue 中的 watcher


Posted in Javascript onDecember 04, 2017

观察 Watchers

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的 watcher 。这是为什么 Vue 提供一个更通用的方法通过watch 选项,来响应数据的变化。当你想要在数据变化响应时,执行异步操作或开销较大的操作,这是很有用的。

大家对于 watch 应该不陌生,项目中都用过下面这种写法:

watch: {
 someProp () {
  // do something
 }
}
// 或者
watch: {
 someProp: {
  deep: true,
  handler () {
   // do something
  }
 }
}

上面的写法告诉 vue,我需要监听 someProp 属性的变化,于是 vue 在内部就会为我们创建一个 watcher 对象。(限于篇幅,我们不聊 watcher 的具体实现,感兴趣的可以直接看源码 watcher)

然而在 vue 中,watcher 的功能并没有这么单一,先上段代码:

<template>
 <div>
  <p>a: {{ a }}</p>
  <p>b: {{ b }}</p>
  <button @click="increment">+</button>
 </div>
</template>
<script>
export default {
 data () {
  return {
   a: 1
  }
 },
 computed: {
  b () {
   return this.a * 2
  }
 },
 watch: {
  a () {
    console.log('a is changed')
  }
 },
 methods: {
  increment () {
   this.a += 1
  }
 },
 created () {
  console.log(this._watchers)
 }
}
</script>

在线demo

上面代码非常简单,我们现在主要关注 created 钩子中打印的 this._watchers,如下:

分别展开三个 watcher,观察每一个 expression,从上到下分别为:

b() {   return this.a * 2;↵  }
"a"
function () {   vm._update(vm._render(), hydrating);↵  }

上面三个 watcher 代表了三种不同功能的 watcher,我们将其按功能分为三类:

  • 在 watch 中定义的,用于监听属性变化的 watcher (第二个)
  • 用于 computed 属性的 watcher (第一个)
  • 用于页面更新的 watcher (第三个)

normal-watcher

我们在 watch 中定义的,都属于这种类型,即只要监听的属性改变了,都会触发定义好的回调函数

computed-watcher

每一个 computed 属性,最后都会生成一个对应的 watcher 对象,但是这类 watcher 有个特点,我们拿上面的 b 举例:

属性 b 依赖 a,当 a 改变的时候,b 并不会立即重新计算,只有之后其他地方需要读取 b 的时候,它才会真正计算,即具备 lazy(懒计算)特性

render-watcher

每一个组件都会有一个 render-watcher, function () {↵ vm._update(vm._render(), hydrating);↵ }, 当 data/computed

中的属性改变的时候,会调用该 render-watcher 来更新组件的视图

三种 watcher 的执行顺序

除了功能上的区别,这三种 watcher 也有固定的执行顺序,分别是:

computed-render -> normal-watcher -> render-watcher

这样安排是有原因的,这样就能尽可能的保证,在更新组件视图的时候,computed 属性已经是最新值了,如果 render-watcher 排在 computed-render 前面,就会导致页面更新的时候 computed 值为旧数据。

下面从一段实例代码中看下vue中的watcher

在这个示例中,使用 watch 选项允许我们执行异步操作(访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这是计算属性无法做到的。

<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- Since there is already a rich ecosystem of ajax libraries -->
<!-- and collections of general-purpose utility methods, Vue core -->
<!-- is able to remain small by not reinventing them. This also -->
<!-- gives you the freedom to just use what you're familiar with. -->
<script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
 // 如果 question 发生改变,这个函数就会运行
question: function (newQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
 // _.debounce 是一个通过 lodash 限制操作频率的函数。
 // 在这个例子中,我们希望限制访问yesno.wtf/api的频率
 // ajax请求直到用户输入完毕才会发出
 // 学习更多关于 _.debounce function (and its cousin
// _.throttle), 参考: https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
var vm = this
if (this.question.indexOf('?') === -1) {
vm.answer = 'Questions usually contain a question mark. ;-)'
return
}
vm.answer = 'Thinking...'
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
},
// 这是我们为用户停止输入等待的毫秒数
500
)
}
})
</script>

小结

本文并不是源码解析类文章,只是从一个角度来聊聊,那些看似不相关的东西(computed/watch/页面更新),内部却有着紧密的联系,希望能抛砖引玉,让大家更深入的去探索 vue

Javascript 相关文章推荐
jquery 学习之一 对象访问
Nov 23 Javascript
浅谈利用JavaScript进行的DDoS攻击原理与防御
Jun 04 Javascript
ES6/JavaScript使用技巧分享
Dec 14 Javascript
JavaScript体验异步更好的解决办法
Jan 08 Javascript
vue基础之v-bind属性、class和style用法分析
Mar 11 Javascript
Vue实现base64编码图片间的切换功能
Dec 04 Javascript
详细介绍解决vue和jsp结合的方法
Feb 06 Javascript
小程序自定义导航栏兼容适配所有机型(附完整案例)
Apr 26 Javascript
Vuex的热更替如何实现
Jun 05 Javascript
vue v-for出来的列表,点击某个li使得当前被点击的li字体变红操作
Jul 17 Javascript
在vue中配置不同的代理同时访问不同的后台操作
Sep 11 Javascript
解决Vue+SpringBoot+Shiro跨域问题
Jun 09 Vue.js
vue中的计算属性的使用和vue实例的方法示例
Dec 04 #Javascript
Vue导出json数据到Excel电子表格的示例
Dec 04 #Javascript
微信小程序tabBar用法实例详解
Dec 04 #Javascript
详解如何实现一个简单的Node.js脚手架
Dec 04 #Javascript
浅谈React和Redux的连接react-redux
Dec 04 #Javascript
bootstrap3中container与container_fluid外层容器的区别讲解
Dec 04 #Javascript
深入浅出webpack之externals的使用
Dec 04 #Javascript
You might like
一个简洁的多级别论坛
2006/10/09 PHP
实用函数8
2007/11/08 PHP
php 实现进制转换(二进制、八进制、十六进制)互相转换实现代码
2010/10/22 PHP
PHP数组无限分级数据的层级化处理代码
2012/12/29 PHP
免费手机号码归属地API查询接口和PHP使用实例分享
2014/04/10 PHP
ThinkPHP实现二级循环读取的方法
2014/11/03 PHP
php实现检查文章是否被百度收录
2015/01/27 PHP
smarty的section嵌套循环用法示例
2016/05/28 PHP
laravel 实现根据字段不同值做不同查询
2019/10/23 PHP
jQuery弹出层插件简化版代码下载
2008/10/16 Javascript
jquery实现简洁文件上传表单样式
2015/11/02 Javascript
学习AngularJs:Directive指令用法(完整版)
2016/04/26 Javascript
jQuery实现手机版页面翻页效果的简单实例
2016/10/05 Javascript
使用jsonp实现跨域获取数据实例讲解
2016/12/25 Javascript
JS正则表达式修饰符中multiline(/m)用法分析
2016/12/27 Javascript
基于react组件之间的参数传递(详解)
2017/09/05 Javascript
JS中Attr的用法详解
2017/10/09 Javascript
React Native 自定义下拉刷新上拉加载的列表的示例
2018/03/01 Javascript
微信小程序的开发范式BeautyWe.js入门详解
2019/07/10 Javascript
详解Vue 换肤方案验证
2019/08/28 Javascript
零基础写python爬虫之爬虫的定义及URL构成
2014/11/04 Python
python编写的最短路径算法
2015/03/25 Python
Python编程图形库之Pillow使用方法讲解
2018/12/28 Python
scrapy爬虫:scrapy.FormRequest中formdata参数详解
2020/04/30 Python
OpenCV Python实现图像指定区域裁剪
2021/03/12 Python
Python内存泄漏和内存溢出的解决方案
2020/09/26 Python
使用CSS3实现字体颜色渐变的实现
2020/08/10 HTML / CSS
Photobook澳大利亚:制作相片书,婚礼卡,旅行相簿
2017/01/12 全球购物
Chantelle仙黛尔内衣美国官网:法国第一品牌内衣
2018/07/26 全球购物
幼儿园校车司机的岗位职责
2014/01/30 职场文书
大学生开西餐厅创业计划书
2014/02/01 职场文书
三严三实对照检查材料
2014/08/25 职场文书
毕业欢送会致辞
2015/07/29 职场文书
MySQL系列之八 MySQL服务器变量
2021/07/02 MySQL
Mysql分析设计表主键为何不用uuid
2022/03/31 MySQL
分享Python异步爬取知乎热榜
2022/04/12 Python