浅谈 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 相关文章推荐
一段实时更新的时间代码
Jul 07 Javascript
javascript+dom树型菜单类,希望朋友们一起进步
May 03 Javascript
由JavaScript技术实现的web小游戏(不含网游)
Jun 12 Javascript
Extjs根据条件设置表格某行背景色示例
Jul 23 Javascript
加随机数引入脚本不让浏览器读取缓存
Sep 04 Javascript
推荐25个超炫的jQuery网格插件
Nov 28 Javascript
EasyUI实现第二层弹出框的方法
Mar 01 Javascript
jquery操作ul的一些操作笔记整理(干货)
Aug 31 jQuery
详解node.js中的npm和webpack配置方法
Jan 21 Javascript
vue中引用swiper轮播插件的教程详解
Aug 16 Javascript
vue2.0 路由模式mode=&quot;history&quot;的作用
Oct 18 Javascript
vue使用Google Recaptcha验证的实现示例
Aug 23 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
外媒评选出10支2020年最受欢迎的Dota2战队
2021/03/05 DOTA
php中将网址转换为超链接的函数
2011/09/02 PHP
str_replace只替换一次字符串的方法
2013/04/09 PHP
php unicode编码和字符串互转的方法
2020/08/12 PHP
PHP实现网站应用微信登录功能详解
2019/04/11 PHP
表格单元格交错着色实现思路及代码
2013/04/01 Javascript
JavaScript中prototype为对象添加属性的误区介绍
2013/10/15 Javascript
jquery按回车提交数据的代码示例
2013/11/05 Javascript
关于页面嵌入swf覆盖div层的问题的解决方法
2014/02/11 Javascript
js判断ie版本号的简单实现代码
2014/03/05 Javascript
在Javascript中处理字符串之big()方法的使用
2015/06/08 Javascript
深入理解JS中的substr和substring
2016/04/26 Javascript
jQuery中DOM节点的删除方法总结(超全面)
2017/01/22 Javascript
jQuery UI实现动画效果代码分享
2018/08/19 jQuery
如何实现一个webpack模块解析器
2018/10/24 Javascript
详解React 元素渲染
2020/07/07 Javascript
针对Vue路由history模式下Nginx后台配置操作
2020/10/22 Javascript
Python 文件和输入输出小结
2013/10/09 Python
Python二维码生成库qrcode安装和使用示例
2014/12/16 Python
Python计算一个文件里字数的方法
2015/06/15 Python
django开发之settings.py中变量的全局引用详解
2017/03/29 Python
Python栈算法的实现与简单应用示例
2017/11/01 Python
Python元组拆包和具名元组解析实例详解
2018/03/26 Python
详解python3中zipfile模块用法
2018/06/18 Python
解决pytorch DataLoader num_workers出现的问题
2020/01/14 Python
惠普加拿大在线商店:HP加拿大
2017/09/15 全球购物
德国机车企业:FC-Moto
2017/10/27 全球购物
海量信息软件测试笔试题
2015/08/08 面试题
营销主管自我评价怎么写
2013/09/19 职场文书
教育学专业毕业生的自我评价
2013/11/21 职场文书
思想政治教育专业个人求职信范文
2013/12/20 职场文书
授权委托书范文
2014/07/31 职场文书
代领毕业证委托书
2014/08/02 职场文书
2015年世界无烟日活动总结
2015/02/10 职场文书
同学聚会感言一句话
2015/07/30 职场文书
python3操作redis实现List列表实例
2021/08/04 Python