Vue2数据响应式原理 | 青训营笔记

81 阅读4分钟

前言

这是我参与「第四届青训营」笔记创作活动的的第6天

下面就Vue2的数据更新原理来谈一谈😀

数据响应式原理:为什么说数据变了,视图也会变?🧐

image.png

MVVM 模型

Vue的开发模式是MVVM,只要数据发生改变,视图也会跟着变化。而且Vue数据变化是非侵入式的。

image.png

侵入与非侵入

  • 非侵入式:简单来讲,就是单纯的引用javascript原生的API,而不是依赖于框架中所提供的封装好的API。非侵入式的优点是可以再写代码时感受不到框架的存在,这意味着可以增强对代码的复用性。Vue的数据变化就是非侵入式的。
// Vue 数据变化
this.a ++;
  • 侵入式:要求用户代码“知道”框架的代码,表现为用户代码需要继承框架提供的类。侵入式让用户代码产生对框架的依赖,这些代码不能在框架外使用,不利于代码的复用。但侵入式可以使用户跟框架更好的结合,更容易更充分的利用框架提供的功能。
// React 数据变化
// 要想改变数据就得调用框架提供的API才行,所以是侵入式
this.setState({
  a: this.state.a + 1
})
// 小程序数据变化
this.setData({
  a: this.data.a + 1
})

Object.defineProperty()

JavaScript为我们提供了Object.defineProperty(),该方法可以做数据劫持或数据代理,意味着可以检测对象属性变化,所以Vue就是主要以这个方法为核心实现了数据响应式。

// 双向数据绑定原理
let obj = {
  name: '张三',
  sex: '男'
  // age: 19
}
// 需要临时变量number,周转getter和setter,才能正常工作
var number;
Object.defineProperty(obj, 'age', {
  // value: 19,
  // enumerable: true, // 控制属性是否可被枚举,默认值为false
  // writable: true, // 控制属性是否可被修改,默认值为false
  // configurable: true, // 控制属性是否可被删除,默认值为false

  // 当有人读取person的属性时,get函数(getter)就会被调用,并且返回的就是age的值
  get() {
    console.log('有人读取age属性')
    return number
  },

  // 当有人修改了person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
  set(value) {
    console.log('有人修改了age属性,且值是', value)
    number = value
  }
})
console.log(person.age) // undefined
obj.age = 100 // 有人修改了age属性,且值是 100
console.log(person.age) // 100

我们习惯把上述代码封装为defineReactive,一个好处是用val参数替代临时变量

let obj = {
  name: '张三',
  sex: '男'
  // age: 19
}

function defineReactive(data, key, val) {
  Object.defineProperty(person, 'age', {
  enumerable: true, // 控制属性是否可被枚举,默认值为false
  configurable: true, // 控制属性是否可被删除,默认值为false

  // 当有人读取person的属性时,get函数(getter)就会被调用,并且返回的就是age的值
  get() {
    console.log('有人读取' + key +'属性')
    return val
  },

  // 当有人修改了person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
  set(newValue) {
    console.log('有人修改了' + key +'属性,且值是', newValue)
    val = newValue
  }
})
}
defineReactive(obj, 'age', 10) // 此时age就是响应式的

上述代码能实现数据的响应式,但是对于多层对象的更深层对象属性是侦测不到了(没有响应式效果)如下:

// eg:
// 此时对于m,n就无法有响应式效果
var obj = {
  a: {
    m: {
      n:{
      ......
      }
    }
  }
}

为了使数据对象上每个属性都是响应式的,那么我们要 递归侦测对象全部属性。

收集依赖

依赖:需要用到数据的地方

  • Vue1.x:细粒度依赖,用到数据的DOM都是依赖

  • Vue2.x:中等粒度依赖,用到数据的组件都是依赖

  • 在getter中收集依赖,在setter中触发依赖

image.png

  • 把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个Observer的实例, 成员中都有一个Dep的实例
  • Watcher是一个中介,数据发生变化时通过Watcher中转,通知组件
  • 依赖就是Watcher。只有Watcher触发的getter才会收集依赖,哪个 Watcher触发了getter,就把哪个Watcher收集到Dep中
  • Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所 有的Watcher都通知一遍
  • 代码实现的巧妙之处:Watcher把自己设置到全局的一个指定位置, 然后读取数据,因为读取了数据,所以会触发这个数据的getter。在 getter中就能得到当前正在读取数据的Watcher,并把这个Watcher 收集到Dep中。

总结

多学源码思想,即学即用,歪瑞故得😎😎😎