Vue响应式原理详解
vue 嘴显著的特性之一便是响应式系统(reactivity system),模型层(model)只是普通javascript对象,修改它则更新视图(view)。
vue 响应式系统的底层细节
如何追踪变化
把一个普通的javascript对象传给vue实例的data选项,vue将遍历此对象的所有属性,并使用object.defineproperty 把这些属性全部转为 getter/setter.object.defineproperty是仅es5支持,并无法shim的特性,这也就是为什么vue不支持ie8以及更低版本浏览器的原因。
用户看不到getter/setter,但是在内部他们让vue跟踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 vue-devtools 来获取更加友好的检查接口。
每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖。之后当依赖项的setter 被调用时,会调用 watcher 重新计算,从而致使它关联的组件得以更新。
变化检测问题
受现代javascript的限制,vue不能检测到对象属性的添加和删除。由于vue会在初始化实例时对属性执行getter/setter转化过程,所以属性必须在data对象上存在才能让vue转化它,这样才能让它是相应的。例如:
var vm=nrew vue({ el:'#app', data:{ a:1 } }); //vm.a是响应的 vm.b=3;//vm.b是非响应的
vue不允许在已经创建的实例上动态添加新的根级响应式属性(root-lever reactive property).然而它可以使用vue.set(object,key,value)方法
将响应属性添加到嵌套的对象上:
vue.set(object.someobject,'b',2);
你哈可以使用vm.$set实例方法,这也是全局vue.set方法的别名:
this.$set(this.someobject,'b',2)
有时你想向已有对象上添加一些属性,例如使用object.assign()或_.extend()方法来添加属性。但是,添加到对象上新属性不会触发更新。这这种情况下可以常见一个新的对象,让它包含元对象的属性和新的属性:
//替换 ‘object.assign(this.someobject,{a:1,b:2})' this.someobject=object.assign({},this.someobject,{a:1,b:2});
声明响应式属性
由于vue不允许动态添加根级响应式属性,所以你必须在初始化实例前声明根级响应式属性,哪怕只是一个空值:
var vm = new vue({ data: { // 声明 message 为一个空值字符串 message: '' }, template: '<div>{{ message }}</div>' }) // 之后设置 `message` vm.message = 'hello!'
如果在data选项中未声明message,vue将警告你渲染函数在视图访问的属性不存在。
这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中一类边界情况,也使vue实例在类型检查系统的帮助下运行的更高效。而且在代码可维护性方面也有一点重要的考虑: data 对象就像组件状态的概要,提前声明所有额响应式属性,可以让组件代码在以后重新阅读或者其他开发人员阅读时更易于被理解。
异步更新队列
vue异步执行dom更新。只要观察到数据变化,vue将开启一个队列,并缓冲在同一个事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和dom操作上非常重要。然后,在下一个事件循环‘tick'中,vue刷新队列并执行实际工作。vue在内部尝试对异步队列使用原生的promise.then,mutationobserver,如果执行环境不支持,会采用settimeout(fn,0)代替。
例如,当你设置vm.somedata='new value',该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个‘tick'更新。多数情况下我们不需要关心这个过程,但是如果你想在dom状态更新后做点儿什么,这就可能会有些棘手。虽然vue.js通常鼓励开发人员沿着‘数据驱动'的方式思考,避免直接接触dom,但是有时我们确实要这么做。为了在数据变化之后等待vue完成更新dom,可以在数据变化之后立即使用vue.nexttick(callback).这样回调函数在dom更新完成后就会调用。例如:
<div id="example">{{message}}</div>
var vm = new vue({ el: '#example', data: { message: '123' } }) vm.message = '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>{{ message }}</span>', data: function () { return { message: 'not updated' } }, methods: { updatemessage: function () { this.message = 'updated' console.log(this.$el.textcontent) // => '没有更新' this.$nexttick(function () { console.log(this.$el.textcontent) // => '更新完成' }) } } })
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: Angular 封装并发布组件的方法示例