写代码啦
Vue响应式原理
回复数(0) 浏览数(74)
{{topic.upvote_count || 0}} 编辑 回复

响应式是vue独特的特性之一,在修改数据后,视图会自动更新
其中的原理是在我们将一个JavaScript对象作为参数传入到Vue中时,Vue会遍历该对象的所有属性,使用 Object.defineProperty 把这些属性全部转为 getter/setter,并且会返回一个组件实例作为该对象的代理。
当修改组件实例的数据时,就会同时修改传入的JavaScript对象的数据,而且vue同时会监听这个JavaScript对象,在JavaScript对象的数据发生修改时,同时也会更新组件实例的数据
每个组件实例都对应一个watcher 实例,watcher 实例会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染

正常情况下vue只会监听实例化之前传进来的数据,若在实例化后增加新属性,vue不会将新属性转化为getter/setter,因为没有setter,所以在新属性的数据发生改变时,就不会通知watcher重新渲染视图
所以可以在传入的参数中预留会用到的属性,这样就可以对属性进行监听
或者是调用vue提供的方法来添加新属性

对象

Vue.set(object, propertyName, value)
或者
vm.$set(object, propertyName, value)

如果要添加多个属性,使用Object.assign() 或 _.extend()也不会让新属性被监听,在这种情况下,你应该用原对象与要混合进去的对象的属性一起创建一个新的对象

this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

数组

vm.$set(vm.items, indexOfItem, newValue)
或者
Vue.set(vm.items, indexOfItem, newValue)
或者
vm.items.splice(indexOfItem, 1, newValue)

但是在某种情况下,即使新属性没有被监听,也会在属性被修改后更新到视图上

<div id="app">
    <span class=span-a>
      {{obj.a}} 
    </span>
    <span class=span-b>
      {{obj.b}}
    </span>
 </div>

var app = new Vue({
  el: '#app',
  data: {
    obj: {
      a: 'a',
    }
  },
})
app.obj.a = 'a2'
app.obj.b = 'b'

上面的代码中app.obj.b的值会被显示在视图上,因为a属性被修改后,通知watch更新视图, 在更新视图的时候,Vue 会去做 diff,发现b的数据也发生了变化,所以也会把b的数据更新到视图上
这里也可以看出vue更新视图是异步的,如果是同步的话上面的代码中,b就不会更新在视图上,只有等到a再次被修改后才可以
vue文档的解释

Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。
这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作
{{topic.upvote_count || 0}}

响应式是vue独特的特性之一,在修改数据后,视图会自动更新
其中的原理是在我们将一个JavaScript对象作为参数传入到Vue中时,Vue会遍历该对象的所有属性,使用 Object.defineProperty 把这些属性全部转为 getter/setter,并且会返回一个组件实例作为该对象的代理。
当修改组件实例的数据时,就会同时修改传入的JavaScript对象的数据,而且vue同时会监听这个JavaScript对象,在JavaScript对象的数据发生修改时,同时也会更新组件实例的数据
每个组件实例都对应一个watcher 实例,watcher 实例会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染

正常情况下vue只会监听实例化之前传进来的数据,若在实例化后增加新属性,vue不会将新属性转化为getter/setter,因为没有setter,所以在新属性的数据发生改变时,就不会通知watcher重新渲染视图
所以可以在传入的参数中预留会用到的属性,这样就可以对属性进行监听
或者是调用vue提供的方法来添加新属性

对象

Vue.set(object, propertyName, value)
或者
vm.$set(object, propertyName, value)

如果要添加多个属性,使用Object.assign() 或 _.extend()也不会让新属性被监听,在这种情况下,你应该用原对象与要混合进去的对象的属性一起创建一个新的对象

this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

数组

vm.$set(vm.items, indexOfItem, newValue)
或者
Vue.set(vm.items, indexOfItem, newValue)
或者
vm.items.splice(indexOfItem, 1, newValue)

但是在某种情况下,即使新属性没有被监听,也会在属性被修改后更新到视图上

<div id="app">
    <span class=span-a>
      {{obj.a}} 
    </span>
    <span class=span-b>
      {{obj.b}}
    </span>
 </div>

var app = new Vue({
  el: '#app',
  data: {
    obj: {
      a: 'a',
    }
  },
})
app.obj.a = 'a2'
app.obj.b = 'b'

上面的代码中app.obj.b的值会被显示在视图上,因为a属性被修改后,通知watch更新视图, 在更新视图的时候,Vue 会去做 diff,发现b的数据也发生了变化,所以也会把b的数据更新到视图上
这里也可以看出vue更新视图是异步的,如果是同步的话上面的代码中,b就不会更新在视图上,只有等到a再次被修改后才可以
vue文档的解释

Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。
这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作
74
回复 编辑