深入响应式原理

大部分的基础内容我们已经讲到了,现在讲点底层内容。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。

变化检测问题

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

  1. var data = { a: 1 }
  2. var vm = new Vue({
  3. data: data
  4. })
  5. // `vm.a` 和 `data.a` 现在是响应的
  6. vm.b = 2
  7. // `vm.b` 不是响应的
  8. data.b = 2
  9. // `data.b` 不是响应的

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

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

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

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

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

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

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

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

初始化数据

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

不这么做:

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

这么做:

  1. var vm = new Vue({
  2. data: {
  3. // 以一个空值声明 `msg`
  4. msg: ''
  5. },
  6. template: '<div>{{msg}}</div>'
  7. })
  8. // 然后设置 `msg`
  9. 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 更新完成后调用。例如:

  1. <div id="example">{{msg}}</div>
  1. var vm = new Vue({
  2. el: '#example',
  3. data: {
  4. msg: '123'
  5. }
  6. })
  7. vm.msg = 'new message' // 修改数据
  8. vm.$el.textContent === 'new message' // false
  9. Vue.nextTick(function () {
  10. vm.$el.textContent === 'new message' // true
  11. })

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

  1. Vue.component('example', {
  2. template: '<span>{{msg}}</span>',
  3. data: function () {
  4. return {
  5. msg: 'not updated'
  6. }
  7. },
  8. methods: {
  9. updateMessage: function () {
  10. this.msg = 'updated'
  11. console.log(this.$el.textContent) // => 'not updated'
  12. this.$nextTick(function () {
  13. console.log(this.$el.textContent) // => 'updated'
  14. })
  15. }
  16. }
  17. })

计算属性的奥秘

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

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

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

  1. var vm = new Vue({
  2. data: {
  3. msg: 'hi'
  4. },
  5. computed: {
  6. example: function () {
  7. return Date.now() + this.msg
  8. }
  9. }
  10. })

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

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

  1. computed: {
  2. example: {
  3. cache: false,
  4. get: function () {
  5. return Date.now() + this.msg
  6. }
  7. }
  8. }

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

Vue.js学习 Item12 – 内部响应式原理探究的更多相关文章

  1. vue学习之深入响应式原理

    vue的响应式原理 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全 ...

  2. 深入浅出Vue基于“依赖收集”的响应式原理(转)

    add by zhj: 文章写的很通俗易懂,明白了Object.defineProperty的用法 原文:https://zhuanlan.zhihu.com/p/29318017 每当问到VueJS ...

  3. Vue.2.0.5-深入响应式原理

    大部分的基础内容我们已经讲到了,现在讲点底层内容.Vue 最显著的一个功能是响应系统 -- 模型只是普通对象,修改它则更新视图.这会让状态管理变得非常简单且直观,不过理解它的原理以避免一些常见的陷阱也 ...

  4. Vue.js 技术揭秘(学习) 深入响应式原理 nextTick外传

    microTask  mutationObserve. promise.then macroTask setImmediate. messageChannnel.setTimeout.postMess ...

  5. vue源码解析之响应式原理

    关于defineReactive等使用细节需要自行了解 一些关键知识点 $mount时 会 new Watcher 把组件的 updateComponent 方法传给watcher 作为getter ...

  6. Vue.js响应式原理

      写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...

  7. 深入解析vue.js响应式原理与实现

    vue.js响应式原理解析与实现.angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很好奇vue.js是如何监测数据更新并且重新渲染页面.vue.js ...

  8. 【Vue源码学习】响应式原理探秘

    最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...

  9. vue.js响应式原理解析与实现

    vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...

随机推荐

  1. 微信消息接收 验证URL有效性 C#代码示例

    官方文档只给出了PHP的示例代码 开发者提交信息后,微信服务器将发送GET请求到填写的URL上,GET请求携带四个参数: 参数 描述 signature 微信加密签名,signature结合了开发者填 ...

  2. gradle android

    从github下载两个开源项目: PagerSlidingTabStrip    |    Android-Universal-Image-Loader-master https://github.c ...

  3. poj 1016 Numbers That Count

    点击打开链接 Numbers That Count Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 17922   Accep ...

  4. canvas-7globleCompositeOperation2.html

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. memcached学习(3)memcached的删除机制和发展方向

    memcached是缓存,所以数据不会永久保存在服务器上,这是向系统中引入memcached的前提. 本次介绍memcached的数据删除机制,以及memcached的最新发展方向--二进制协议(Bi ...

  6. c# 获取excel所有工作表

    var filePath="f:\xx.xlsx" string connStr = "Provider=Microsoft.Ace.OleDb.12.0;" ...

  7. #define的一些

    // 生成一个字符串 #define NSString(...) [NSString stringWithFormat:__VA_ARGS__]

  8. IList,IQeurable,IEnumble和List 的区别

    IList,IQeurable,IEnumble和List 的区别主要如下: 1.IList(IList<T>)会立即在内存里创建持久数据,这就没有实现“延期执行(deferred exe ...

  9. SQL:每年每月最高的两个温度

    SET QUOTED_IDENTIFIER ONGO CREATE TABLE [dbo].[Temperature]( [ID] [int] IDENTITY(1,1) NOT NULL, [Mon ...

  10. Cnetos7下,已经能访问tomcat

    进入/usr/local/apache-tomcat-8.0.24/bin 中 执行:./startup.sh开启tomcat 再执行如下 systemctl stop firewalld.servi ...