关闭

Vue的响应式原理

标签: vuejavascript对象响应式原理watcher
415人阅读 评论(0) 收藏 举报
分类:

下面的是一张来自Vue官网的图片:

追踪变化

如何追踪变化??

当把一个普通的JavaScript对象传递给Vue实例的data选项,Vue将遍历这个对象的所有属性,并且利用Object.defineProperty这些属性全部转换为getter/setter,当然由于兼容性问题,所以Vue不支持IE8及更低版本的浏览器

从上面的图可以看出,对于数据Data来说,它里面的getter与setter是对于外部是不可见的,但是在内部完全可以使用它们,在内部,通过Vue进行追踪依赖,当属性被修改或者访问时通知变化。每个组件的实例都会有相应的watcher实例对象,它会在组件渲染的过程中将属性记为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而让和它相关联的组件得以更新

变化的检测问题

受现代浏览器的限制(以及废弃的Object.observe),Vue不能检测到对象属性的添加或者删除,由于Vue在初始的时候就会对属性实行getter/setter转换过程,所以属性必须存在于data对象时才能让Vue转换它,这样它才是响应的,这段话的意思等同于下面的代码:

        //初始化之前写在data中的可以让Vue转换它,所以a是响应的
        var v = new Vue({
            data : {
                a : 1
            }
        });

        //初始化之后添加的属性b是非响应的
        v.b = 2;

Vue不允许在已经创建的实例上动态添加新的根级响应式属性,然而它可以使用Vue.set(Object, key, value)方法将响应属性添加到嵌套的对象上

Vue.set(vm.someObject, 'b', 2)

也可以使用v.$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.thenMutationObserver,如果执行环境不支持,会采用setTimeout(fn, 0)代替

例如,当你设置了vm.someData = 'new value',该组件不会立即重新渲染,当刷新队列时,组件会在事件循环队列清空的下一个”tick”更新,多数情况下我们不需要关心这个过程,但是如果你想在DOM状态更新后做点什么,这就可能会有点困难,为了使得数据变化之后等待Vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick(callback) 这样回掉函数在DOM更新后就会调用。

    <div id="example">{{message}}</div>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#example',
            data: {
                message: '123'
            }
        });
        vm.message = 'new message' // 更改数据
        console.log(vm.$el.textContent);   //123

        Vue.nextTick(function () {
            alert(2323);
            console.log(vm.$el.textContent);  //new message
        });
    </script>

在组件内使用vm.$nextTick()实例方法特别方便,因为它不需要全局Vue,并且回掉函数中的this将自动绑定到当前的Vue实例上

        Vue.component('my-component', {
            template: '<span> {{message}}</span>',
            data : function() {
                return {
                    message: 'not update'
                }
            },
            methods : {
                updateMessage : function() {
                    this.message = 'update';
                    console.log(this.$el.textContent);  //'not update'
                    this.$nextTick(function() {
                       console.log(this.$el.textContent); //'update'
                    });
                }
            }
        });

参考:https://cn.vuejs.org/v2/guide/reactivity.html

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:86671次
    • 积分:2004
    • 等级:
    • 排名:千里之外
    • 原创:125篇
    • 转载:9篇
    • 译文:0篇
    • 评论:9条
    博客专栏
    最新评论