浅谈 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 相关文章推荐
经典的解除许多网站无法复制文字的绝招
Dec 31 Javascript
Jquey拖拽控件Draggable使用方法(asp.net环境)
Sep 28 Javascript
在jquery中的ajax方法怎样通过JSONP进行远程调用
Apr 04 Javascript
使用coffeescript编写node.js项目的方法汇总
Aug 05 Javascript
jQuery与Ajax以及序列化
Feb 01 Javascript
jquery 实现回车登录详解及实例代码
Oct 23 Javascript
最常见的左侧分类菜单栏jQuery实现代码
Nov 28 Javascript
vue-resource请求实现http登录拦截或者路由拦截的方法
Jul 11 Javascript
解决vue单页路由跳转后scrollTop的问题
Sep 03 Javascript
element UI upload组件上传附件格式限制方法
Sep 04 Javascript
小程序转发探索示例
Feb 19 Javascript
Vue+Element UI+vue-quill-editor富文本编辑器及插入图片自定义
Aug 20 Javascript
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
长波有什么东西
2021/03/01 无线电
php桌面中心(一) 创建数据库
2007/03/11 PHP
ThinkPHP入库出现两次反斜线转义及数据库类转义的解决方法
2014/11/04 PHP
php获取发送给用户的header信息的方法
2015/03/16 PHP
PHP全功能无变形图片裁剪操作类与用法示例
2017/01/10 PHP
ThinkPHP3.1.x修改成功与失败跳转页面的方法
2017/09/29 PHP
php实现获取农历(阴历)、节日、节气的类与用法示例
2017/11/20 PHP
PHP迭代器和生成器用法实例分析
2019/09/28 PHP
页面中js执行顺序
2009/11/09 Javascript
自写的一个jQuery圆角插件
2010/10/26 Javascript
JSON+JavaScript处理JSON的简单例子
2013/03/20 Javascript
百度UEditor编辑器如何关闭抓取远程图片功能
2015/03/03 Javascript
js实现鼠标划过给div加透明度的方法
2015/05/25 Javascript
JavaScript中的call和apply的用途以及区别
2017/01/11 Javascript
js实现固定宽高滑动轮播图效果
2017/01/13 Javascript
小程序实现订单倒计时功能
2019/04/23 Javascript
nodejs使用node-xlsx生成excel的方法示例
2019/08/22 NodeJs
解决layui弹框失效的问题
2019/09/09 Javascript
详解JavaScript的this指向和绑定
2020/09/08 Javascript
Python解释执行原理分析
2014/08/22 Python
python中 chr unichr ord函数的实例详解
2017/08/06 Python
Python中执行存储过程及获取存储过程返回值的方法
2017/10/07 Python
pycharm修改文件的默认打开方式的步骤
2019/07/29 Python
python实现生成Word、docx文件的方法分析
2019/08/30 Python
Python开发之基于模板匹配的信用卡数字识别功能
2020/01/13 Python
Python实例教程之检索输出月份日历表
2020/12/16 Python
CSS3的transition和animation的用法实例介绍
2014/08/20 HTML / CSS
HTML5手指下滑弹出负一屏阻止移动端浏览器内置下拉刷新功能的实现代码
2020/04/10 HTML / CSS
医学专业毕业生个人求职信
2013/12/25 职场文书
领导的自我鉴定
2013/12/28 职场文书
房地产营销策划方案
2014/02/08 职场文书
行政专员的岗位职责
2014/03/10 职场文书
离婚起诉状范本
2015/05/19 职场文书
2016年大学校运会广播稿件
2015/12/21 职场文书
教你如何使用Python Tkinter库制作记事本
2021/06/10 Python
小程序实现侧滑删除功能
2022/06/25 Javascript