关闭

Vue.js内部响应式原理探究

2089人阅读 评论(0) 收藏 举报
分类:

最近在使用vue.js,在jb51网站上看到一篇不错的文章,转载一下:

深入响应式原理

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

 setter 被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。



变化检测问题

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

?
1
2
3
4
5
6
7
8
9
10
11
vardata = { a: 1 }
varvm = newVue({
 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 对象上声明所有的响应属性。

不这么做:

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

这么做:

varvm = newVue({
 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>

varvm = newVue({
 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 不总是被调用。考虑下例:

varvm = newVue({
 data: {
 msg:'hi'
 },
 computed: {
 example:function() {
  returnDate.now() + this.msg
 }
 }
})

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

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

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

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


如果意犹未尽,可以前去原作者网站:http://www.jb51.net/Special/874.htm,如果还是不满足,直接阅读vue的源码吧

1
0
查看评论

vue 深入响应式原理 注意事项

对vue.js 的响应式的原理的理解,有助于更加灵活的使用vue,和避开一些坑 所以了解其运行原理十分重要
  • wngzhem
  • wngzhem
  • 2016-12-13 17:19
  • 907

Vue 响应式原理

将data与dom绑定的原理 首先引用官网的一张图 要将data与dom绑定,首先要满足一下几点 1、Vue实例内预先定义了data 2、使用Vue.set(object, key, value)或者vm.$set(object, key, value)方法设置了data因为Vue实例创建的...
  • qq_18837459
  • qq_18837459
  • 2017-07-20 15:23
  • 175

Vue的响应式原理

下面的是一张来自Vue官网的图片:如何追踪变化??当把一个普通的JavaScript对象传递给Vue实例的data选项,Vue将遍历这个对象的所有属性,并且利用Object.defineProperty这些属性全部转换为getter/setter,当然由于兼容性问题,所以Vue不支持IE8及更低版本...
  • Dear_Mr
  • Dear_Mr
  • 2017-05-15 23:09
  • 611

理解vue实现原理,实现一个简单的Vue框架

参考: 剖析Vue实现原理 - 如何实现双向绑定mvvm Vue.js源码(1):Hello World的背后 Vue.js官方工程本文所有代码可以在git上找到。       其实对JS我研究不是太深,用过很多次,但只是实现功能就算了。最近JS实在是太...
  • pur_e
  • pur_e
  • 2016-11-07 14:51
  • 25188

Vue.js的常见用法及基本原理

Vue.js 快速入门 什么是Vue.js vue是法语中视图的意思,Vue.js是一个轻巧、高性能、可组件化的MVVM库,同时拥有非常容易上手的API。作者是尤雨溪,写下这篇文章时vue.js版本为1.0.7 准备 我推荐使用su...
  • jllovekd
  • jllovekd
  • 2017-02-08 09:42
  • 5467

理解vue实现原理,实现一个简单的Vue框架

原文地址:http://blog.csdn.net/pur_e/article/details/53066275       其实对JS我研究不是太深,用过很多次,但只是实现功能就算了。最近JS实在是太火,从前端到后端,应用越来越广泛,各种框架层出不穷,忍...
  • tanga842428
  • tanga842428
  • 2017-02-07 09:54
  • 4231

vue深入相应式原理

Vue 最显著的特性之一便是不太引人注意的响应式系统(reactivity system)。模型层(model)只是普通 JavaScript 对象,修改它则更新视图(view)。这会让状态管理变得非常简单且直观,不过理解它的工作原理以避免一些常见的问题也是很重要的。在本节中,将开始深入挖掘 Vue...
  • zsy619
  • zsy619
  • 2017-03-04 11:18
  • 320

Vue的缓存算法—LRU算法

最近在看Vue的源码,不得不说的是,Vue的源码十分优雅简洁,下面就来分享下Vue的缓存利用的算法LRU算法。LRU算法LRU是Least recently used的简写,主要原理是根据历史访问记录来淘汰数据,说白了就是这个算法认为如果数据被访问过,那么将来被访问的几率也高。其存储结构是一个双链表...
  • zp1996323
  • zp1996323
  • 2016-09-24 15:38
  • 723

vue-router的使用及实现原理

前端路由是直接找到与地址匹配的一个组件或对象并将其渲染出来。改变浏览器地址而不向服务器发出请求有两种方式: 1. 在地址中加入#以欺骗浏览器,地址的改变是由于正在进行页内导航 2. 使用H5的window.history功能,使用URL的Hash来模拟一个完整的URL。当打包构建应用时,Java...
  • caoxinhui521
  • caoxinhui521
  • 2017-08-29 21:35
  • 5526

Vuejs的学习笔记1---------响应式原理

Vuejs采用的基于依赖收集的观测机制        1 将原生的数据改造成可观测对象,一个可观测的对象可以被取值,也可以被赋值        2 在watcher的求值过程中,每一个被取值的可观测对象都会将...
  • YinWeiLaiBuJi
  • YinWeiLaiBuJi
  • 2016-10-26 14:57
  • 666
    个人资料
    • 访问:339960次
    • 积分:5548
    • 等级:
    • 排名:第5692名
    • 原创:226篇
    • 转载:111篇
    • 译文:1篇
    • 评论:38条
    最新评论