Vue.js每天必学之内部响应式原理探究


Posted in Javascript onSeptember 07, 2016

深入响应式原理

大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue.js 最显著的一个功能是响应系统 —— 模型只是普通对象,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题。下面我们开始深挖 Vue.js 响应系统的底层细节。

如何追踪变化

把一个普通对象传给 Vue 实例作为它的 data 选项,Vue.js 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。这是 ES5 特性,不能打补丁实现,这便是为什么 Vue.js 不支持 IE8 及更低版本。

用户看不到 getter/setters,但是在内部它们让 Vue.js 追踪依赖,在属性被访问和修改时通知变化。一个问题是在浏览器控制台打印数据对象时 getter/setter 的格式化不同,使用 vm.$log() 实例方法可以得到更友好的输出。

模板中每个指令/数据绑定都有一个对应的 watcher 对象,在计算过程中它把属性记录为依赖。之后当依赖的 setter 被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。

Vue.js每天必学之内部响应式原理探究

变化检测问题

受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:

var data = { a: 1 }
var vm = new Vue({
 data: data
})
// `vm.a` 和 `data.a` 现在是响应的

vm.b = 2
// `vm.b` 不是响应的

data.b = 2
// `data.b` 不是响应的

不过,有办法在实例创建之后添加属性并且让它是响应的。

对于 Vue 实例,可以使用 $set(key, value) 实例方法:

vm.$set('b', 2)
// `vm.b` 和 `data.b` 现在是响应的

对于普通数据对象,可以使用全局方法 Vue.set(object, key, value):

Vue.set(data, 'c', 3)
// `vm.c` 和 `data.c` 现在是响应的

有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 添加属性。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性:

// 不使用 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

也有一些数组相关的问题,之前已经在列表渲染中讲过。

初始化数据

尽管 Vue.js 提供了 API 动态地添加响应属性,还是推荐在 data 对象上声明所有的响应属性。

不这么做:

var vm = new Vue({
 template: '<div>{{msg}}</div>'
})
// 然后添加 `msg`
vm.$set('msg', 'Hello!')

这么做:

var vm = new Vue({
 data: {
 // 以一个空值声明 `msg`
 msg: ''
 },
 template: '<div>{{msg}}</div>'
})
// 然后设置 `msg`
vm.msg = 'Hello!'

这么做有两个原因:
 1.data 对象就像组件状态的模式(schema)。在它上面声明所有的属性让组件代码更易于理解。

 2.添加一个顶级响应属性会强制所有的 watcher 重新计算,因为它之前不存在,没有 watcher 追踪它。这么做性能通常是可以接受的(特别是对比 Angular 的脏检查),但是可以在初始化时避免。 

异步更新队列

Vue.js 默认异步更新 DOM。每当观察到数据变化时,Vue 就开始一个队列,将同一事件循环内所有的数据变化缓存起来。如果一个 watcher 被多次触发,只会推入一次到队列中。等到下一次事件循环,Vue 将清空队列,只进行必要的 DOM 更新。在内部异步队列优先使用 MutationObserver,如果不支持则使用 setTimeout(fn, 0)。

例如,设置了 vm.someData = 'new value',DOM 不会立即更新,而是在下一次事件循环清空队列时更新。我们基本不用关心这个过程,但是如果想在 DOM 状态更新后做点什么,这会有帮助。尽管 Vue.js 鼓励开发者沿着数据驱动的思路,避免直接修改 DOM,但是有时确实要这么做。为了在数据变化之后等待 Vue.js 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback) 。回调在 DOM 更新完成后调用。例如:

<div id="example">{{msg}}</div>

var vm = new Vue({
 el: '#example',
 data: {
 msg: '123'
 }
})
vm.msg = 'new message' // 修改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
 vm.$el.textContent === 'new message' // true
})

vm.$nextTick() 这个实例方法比较方便,因为它不需要全局 Vue,它的回调的 this 自动绑定到当前 Vue 实例:

Vue.component('example', {
 template: '<span>{{msg}}</span>',
 data: function () {
 return {
 msg: 'not updated'
 }
 },
 methods: {
 updateMessage: function () {
 this.msg = 'updated'
 console.log(this.$el.textContent) // => 'not updated'
 this.$nextTick(function () {
 console.log(this.$el.textContent) // => 'updated'
 })
 }
 }
})

计算属性的奥秘

你应该注意到 Vue.js 的计算属性不是简单的 getter。计算属性持续追踪它的响应依赖。在计算一个计算属性时,Vue.js 更新它的依赖列表并缓存结果,只有当其中一个依赖发生了变化,缓存的结果才无效。因此,只要依赖不发生变化,访问计算属性会直接返回缓存的结果,而不是调用 getter。

为什么要缓存呢?假设我们有一个高耗计算属性 A,它要遍历一个巨型数组并做大量的计算。然后,可能有其它的计算属性依赖 A。如果没有缓存,我们将调用 A 的 getter 许多次,超过必要次数。

由于计算属性被缓存了,在访问它时 getter 不总是被调用。考虑下例:

var vm = new Vue({
 data: {
 msg: 'hi'
 },
 computed: {
 example: function () {
 return Date.now() + this.msg
 }
 }
})

计算属性 example 只有一个依赖:vm.msg。Date.now() 不是 响应依赖,因为它跟 Vue 的数据观察系统无关。因而,在访问 vm.example 时将发现时间戳不变,除非 vm.msg 变了。

有时希望 getter 不改变原有的行为,每次访问 vm.example 时都调用 getter。这时可以为指定的计算属性关闭缓存:

computed: {
 example: {
 cache: false,
 get: function () {
 return Date.now() + this.msg
 }
 }
}

现在每次访问 vm.example 时,时间戳都是新的。但是,只是在 JavaScript 中访问是这样的;数据绑定仍是依赖驱动的。如果在模块中这样绑定计算属性 {{example}},只有响应依赖发生变化时才更新 DOM。

本文已被整理到了《Vue.js前端组件学习教程》,欢迎大家学习阅读。

关于vue.js组件的教程,请大家点击专题vue.js组件学习教程进行学习。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
三种方式获取XMLHttpRequest对象
Apr 21 Javascript
Js+Jq获取URL参数的集中方法示例代码
May 20 Javascript
javascript学习笔记--数字格式类型
May 22 Javascript
jquery实现将获取的颜色值转换为十六进制形式的方法
Dec 20 Javascript
jQuery自动完成插件completer附源码下载
Jan 04 Javascript
总结在前端排序中遇到的问题
Jul 19 Javascript
jquery中用函数来设置css样式
Dec 22 Javascript
React Native之ListView实现九宫格效果的示例
Aug 02 Javascript
AngularJS 仿微信图片手势缩放的实例
Sep 28 Javascript
详解JS判断页面是在手机端还是在PC端打开的方法
Apr 26 Javascript
Node 代理访问的实现
Sep 19 Javascript
mpvue 页面预加载新增preLoad生命周期的两种方式
Oct 17 Javascript
在JavaScript中调用Java类和接口的方法
Sep 07 #Javascript
Vue.js每天必学之指令系统与自定义指令
Sep 07 #Javascript
Vue.js每天必学之过滤器与自定义过滤器
Sep 07 #Javascript
详解AngularJS中ng-src指令的使用
Sep 07 #Javascript
AngularJS实现按钮提示与点击变色效果
Sep 07 #Javascript
JS实现简单易用的手机端浮动窗口显示效果
Sep 07 #Javascript
利用Angularjs实现幻灯片效果
Sep 07 #Javascript
You might like
第三章 php操作符与控制结构代码
2011/12/30 PHP
Yii框架中 find findAll 查找出制定的字段的方法对比
2014/09/10 PHP
JAVASCRIPT HashTable
2007/01/22 Javascript
javascript 类型判断代码分析
2010/03/28 Javascript
js 多浏览器分别判断代码
2010/04/01 Javascript
jQuery UI AutoComplete 使用说明
2011/06/20 Javascript
jQuery 数据缓存模块进化史详细介绍
2012/11/19 Javascript
Javascript基础 函数“重载” 详细介绍
2013/10/25 Javascript
javascript运行机制之this详细介绍
2014/02/07 Javascript
Jquery遍历checkbox获取选中项value值的方法
2014/02/13 Javascript
原生Ajax 和jQuery Ajax的区别示例分析
2014/12/17 Javascript
Jquery api 速查表分享
2015/01/12 Javascript
jQuery实现鼠标划过修改样式的方法
2015/04/14 Javascript
JavaScript中Boolean对象的属性解析
2015/10/21 Javascript
详解Jquery的事件操作和文档操作
2016/12/19 Javascript
JavaScript trim 实现去除字符串首尾指定字符的简单方法
2016/12/27 Javascript
用file标签实现多图文件上传预览
2017/02/14 Javascript
一篇看懂vuejs的状态管理神器 vuex状态管理模式
2017/04/20 Javascript
JavaScript实现一个简易的计算器实例代码
2018/05/10 Javascript
大转盘抽奖小程序版 转盘抽奖网页版
2020/04/16 Javascript
vue商城中商品“筛选器”功能的实现代码
2020/07/01 Javascript
Element Popover 弹出框的使用示例
2020/07/26 Javascript
vue 将多个过滤器封装到一个文件中的代码详解
2020/09/05 Javascript
小程序中手机号识别的示例
2020/12/14 Javascript
el-table表头根据内容自适应完美解决表头错位和固定列错位
2021/01/07 Javascript
[01:03:27]Optic vs VGJ.S 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
Python中的Socket 与 ScoketServer 通信及遇到问题解决方法
2019/04/01 Python
快速了解Python开发环境Spyder
2020/06/29 Python
如何用Matlab和Python读取Netcdf文件
2021/02/19 Python
自我评价正确写法范文
2013/12/10 职场文书
大专生找工作自荐书
2014/06/10 职场文书
迎新晚会策划方案
2014/06/13 职场文书
2014年保育员工作总结
2014/12/02 职场文书
民主评议党员个人总结
2015/02/13 职场文书
导游词之淮安明祖陵
2019/11/25 职场文书
阿里云服务器Ubuntu 20.04上安装Odoo 15
2022/05/20 Servers