Vue源码学习(二)数据响应式

127 阅读3分钟

Vue2.0中为了实现数据响应式通过使用Object.defineProperty添加getter和setter对数据变更进行了拦截,并在数据获取时收集依赖,在数据更改时进行通知对应的依赖执行更新函数。

数据的添加和删除

Object.defineProperty只能对对象进行拦截,因此Vue2.0中的响应式操作基本上是通过拦截data对象的key完成的,但是Object.defineProperty无法拦截对象属性的添加和删除,因此添加和删除响应式数据需要通过Vue.set和Vue.delete来进行通知

数据响应式初始化

在vue数据响应式的初始化过程有几个重要的类和方法Observer、Dep、Watcher、observe、defineReactive

observe

observe是一个返回响应式数据实例的方法

它会检查给定的值是否是一个对象,如果不是会直接return,否则会继续检查它是否是一个Observer实例,如果是的话直接返回这个Observer实例,否则会通过Observer创建一个实例然后返回。

简易源码展示

export function observe (value) {
  if (!isObject(value)) {
    return
  }

  let ob
  if (value instanceof Observer) {
    ob = value
  } else {
    ob = new Observer(value)
  }
  return ob
}

Observer

Observer是一个将对象变为响应式数据的类

Observer

  1. 为对象创建Dep对象
  2. 判断对象是不是数组,如果是数组则通过覆盖7个会改变原数组的方法来进行通知和拦截,并循环数组执行observe,对每个元素执行响应式处理;这也是为什么通过arr[1] = 1无法触发响应式的原因
    • push
    • pop
    • shift
    • unshift
    • splice
    • sort
    • reverse
  3. 如果不是则遍历对象依次为每个key执行defineReactive进行对象key的拦截

defineReactive

defineReact是一个负责使用Object.defineProperty对对象的key进行拦截的函数

  1. 使用Dep创建dep
  2. 使用observe对obj[key]进行响应式处理,返回子ob
  3. 使用Object.defineProperty为obj的key添加setter和getter

getter

当访问key时,判断Dep.target上是否存在值,如果存在则使用dep.depend()收集依赖,如果子ob存在,则子ob也进行依赖收集,如果是数组,则循环数组进行收集

setter

当key的值改变时,判断新值和旧值是否相同,不同则替换新值,然后对新值进行响应式处理,最后调用dep.notify()通知变更

Dep

Dep是一个收集并存储依赖,通知更新的类

Dep主要分为负责数据变更和数据增删两种类型的Dep

在Observer类创建的主要负责数据的增删通知,可以称它为大管家,主要在Vue.set、Vue.delete和数组变更或对象类型增删时使用

在defineReactive中创建的则负责数据的变更通知,一般叫它小管家,主要负责基础类型的数据变更时使用

Watcher

Watcher用来触发更新,主要分为render watcher和user watcher

render watcher每个组件会产生一个,主要用来执行组件的render和update函数来更新页面

user watcher一般是用户创建的watch,它则负责执行用户的逻辑

Dep和Watcher

vue中由于Watcher分为render watcher和user watcher,render watcher的粒度为每个组件一个watcher,而每个组件中有多个dep,因此dep和watcher是多对多的关系,因此Dep和Watcher是互相持有的关系,当数据发生改变时,Dep会通知所持有的多个依赖,即watcher进行更新,而当watcher中有不需要的dep时需要通知dep移除自身,以免之后数据改变时,不需要的dep继续通知自己更新