Vue2源码解析-响应式原理

103 阅读4分钟

Vue的响应式原理主要用到了ES5的Object.defineProperty(obj, prop, descriptor),它能传三个参数

  • obj:需要定义属性的对象
  • prop:是需要定义或修改的属性的名称
  • descriptor:需要被定义或修改的属性描述符。

我们主要看descriptor,它有很多可选键值

  • configurable:当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false
  • enumerable:当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false
  • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
  • writable:当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US)改变。默认为 false

除了上面几个可选键值外,主要还有 get 和 set

  • get:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined
  • set:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

在Vue中一旦对象拥有了gettersetter,我们可以简单地把这个对象称为响应式对象,下面来看下响应式对象的具体是怎么样的。

响应式对象

在Vue初始化的时候,执行了_init方法,定义在vue/src/core/instance/init.js,该方法中执行了initState方法,定义在vue/src/core/instance/state.js里面。

Snipaste_2022-04-03_13-31-00.png

这里主要初始化了props,methods,data,computed,watch等,我们可以通过this.message就能访问到data中定义的属性就是通过Object.defineProperty方法,通过_data代理到我们真正的data

现在再来看下initData,除了代理外还做了响应式操作

Snipaste_2022-04-03_13-38-19.png

initData最后执行了observe方法,它定义在vue/src/core/observe/index.js,该方法主要执行了new Observer(value)方法

Snipaste_2022-04-03_13-42-01.png

这个Observer类主要做了

  1. 初始化oberve实例,赋值给传入data__ob__属性
  2. 执行walk(因为一开始传入data是对象)

Snipaste_2022-04-03_13-45-33.png

而这个walk方法主要遍历传入的data对象,并将该对象和key传入defineReactive,这个defineReactive方法就是响应式原理的核心方法

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

我们可以看到这个方法中又执行let childOb = !shallow && observe(val),它就会判断如果属性又是对象就会递归调用observer让每一层都会转换成响应式对象

而这个方法的的核心就是在对传入的遍历属性定义getset,下面我们来看看getset做了什么

依赖收集

Dep

当执行defineReactive方法中,首先执行了const dep = new Dep(),定义在vue/src/core/observer/dep.js中,它有一个静态属性target,它会存放同一时间只能有一个的全局的Watcher

Snipaste_2022-04-03_14-01-37.png

Watcher

Dep主要是用来收集Watcher,而Watcher是什么呢,我们在mountComponent的时候会初始化new Watcher

Snipaste_2022-04-03_14-07-57.png

定义在vue/src/core/observe/watcher.js

Snipaste_2022-04-03_14-10-13.png

WatcherDep是互不分离的,而Dep是整个getter依赖收集的核心。

下面来看下getter主要做了什么

get: function reactiveGetter () {
  const value = getter ? getter.call(obj) : val
  if (Dep.target) {
    dep.depend()
    if (childOb) {
      childOb.dep.depend()
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
  }
  return value
}
  1. 执行dep.depend将各种Watcher收集到dep实例的subs
  2. childOb.dep.depend(),如果是对象则再对对象的dep收集Watcher
  3. 如果传入的数组则执行dependArray(value)进行遍历,若存在__ob__执行e.__ob__.dep.depend()收集Watcher

派发更新

set: function reactiveSetter (newVal) {
  const value = getter ? getter.call(obj) : val
  /* eslint-disable no-self-compare */
  if (newVal === value || (newVal !== newVal && value !== value)) {
    return
  }
  /* eslint-enable no-self-compare */
  if (process.env.NODE_ENV !== 'production' && customSetter) {
    customSetter()
  }
  // #7981: for accessor properties without setter
  if (getter && !setter) return
  if (setter) {
    setter.call(obj, newVal)
  } else {
    val = newVal
  }
  childOb = !shallow && observe(newVal)
  dep.notify()
}

setter主要执行了dep.notify()

notify

Snipaste_2022-04-03_14-24-26.png

这里将之前getter收集存放Watchersubs属性进行遍历,执行subs[i].update(),即执行watcher.update(),定义在vue/src/core/observe/watcher.js

Snipaste_2022-04-03_14-27-31.png

这里主要执行queueWatcher,定义在vue/src/core/observe/scheduler.js

Snipaste_2022-04-03_14-29-21.png

flushSchedulerQueue

这是一个调度器,它这里做了优化,将需要执行Watcher先推入到queue队列中,最后调用 nextTick(flushSchedulerQueue),当同一时间循环下,现将各种各样的Watcher都推入到队列中,在下一个时间tick中再将队列取出执行flushSchedulerQueue

Snipaste_2022-04-03_14-34-37.png

run

这里主要执行watcher.run()

Snipaste_2022-04-03_14-36-30.png

可以看到主要调用了const value = this.get()

get

Snipaste_2022-04-03_14-40-20.png

在调用get方法中执行了this.getter,就是传进来的updateComponent回调

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

所以会重新renderpatch