Vue中的响应式原理

Vue最独特的特性之一,是其非侵入性的响应式系统

响应式原理:数据变,页面变

Vue如何追踪变化

当把一个普通的JS对象传入Vue实例作为data选项时,Vue将遍历此对象的所有属性,并使用Object.defineProperty把这些属性全部转为getter/setter。Object.defineProperty是ES5中一个无法shim的特性,这也是Vue不支持IE8以及更低版本浏览器的原因。

Object.defineProperty(obj, prop, descriptor)
// descriptor有两种主要形式:数据描述符和存取描述符,描述符必须是两种形式之一,不能同时是两者
// 数据描述符
descriptor: {
  configurable: false, // 默认false, 表示不可配置
  enumarable: false, // 默认false, 表示不可枚举
  value: undefined, // 属性对应的值
  writable: false // 默认false,表示不可写
}
// 存取描述符
descriptor: {
  configurable: false, // 同上
  enumerable: false, // 同上
  get () {} , // 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined
  set () {} // 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined
}

这些getter/setter对用户来说是不可见,但是在内部它们让Vue能够追踪依赖,在属性被访问和修改时通知变更。

每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把‘接触’过的数据属性记录为依赖。之后当依赖项的setter/getter触发时,会通知watcher,从而使它关联的组件重新渲染。

检测变化

受现代JS的限制(而且Object.observe----用于异步地监视一个对象的改变。当对象属性被修改时,方法的回调函数会提供一个有序的修改流。也已经被废弃), Vue无法检测到对象属性的添加或删除。所以属性必须在data对象上存在才能让Vue将它转换为响应式的。

对于已经创建的实例,Vue不允许动态添加根级别的响应式属性。但是可以通过使用,Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性,内部使用的就是object.defineProperty。

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

还可以使用vm.$set实例方法,这也是全局Vue.set方法的别名:

this.$set(this.someObject, 'b', 2)

有时可能需要为已有对象赋值多个新属性,比如使用Object.assign()或_.extend(), 但是,这样添加到对象上的新属性不会触发更新。在这种情况下,可以让属性指向一个新的有原对象于要混合进去的对象的属性一起创建一个新的对象。

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

声明响应式属性

由于Vue不允许动态添加根级响应式属性,所以必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值;如果未在data选项中声明message,Vue将警告渲染函数正在试图访问不存在的属性。

这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中的一类边界情况,也使 Vue 实例能更好地配合类型检查系统工作。但与此同时在代码可维护性方面也有一点重要的考虑:data 对象就像组件状态的结构 (schema)。提前声明所有的响应式属性,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。

 异步更新队列

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

然后,在下一个事件循环‘tick'中,Vue刷新队列并执行实际(已去重)的工作。Vue在内部对异步队列尝试使用原生的Promise.thenMutationObserversetImmediate, 如果执行环境不支持,则会采用setTimeout(fn, 0)代替。

因为组件会在下一个事件循环’tick'中更新。所以如果想基于更新后的DOM状态来做点什么,会有些棘手。为此,Vue提供了nextTick,为了在数据变化之后等待Vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数将在DOM更新完成后被调用。

模拟实现响应式

dom结构:

<!-- DOM结构 -->
<div id="test">
</div>
  <button onclick="change()">change</button>

js代码:

function change() {
  data.age = 999
}

let data = {
}
let age = 2
let test = document.getElementById("test")
test.innerHTML = age
Object.defineProperty(data, 'age', {
  get () {
    return age
  },
  set (params) {
    age = params
    wathcer()
  }
})
// 绑定watcher监听
function wathcer () {
  // 数据改变之后触发,进行diff, 判断是否应该修改dom
  renderDom()
}
// 重新渲染dom
function renderDom(h) {
  let test = document.getElementById("test")
  let age = data.age
  test.innerHTML = age
}

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

猜你喜欢

转载自www.cnblogs.com/jett-woo/p/12512634.html