Vue 源码-响应式数据

59 阅读2分钟

vue版本:v2.7.10

首先看一下数据初始化函数initData。

src/core/instance/state.ts

function initData(vm: Component) {
  let data: any = vm.$options.data
  data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
  ...
  const ob = observe(data)
  ...
}

export function observe(
  value: any,
  shallow?: boolean,
  ssrMockReactivity?: boolean
): Observer | void {
  ...
  ob = new Observer(value, shallow, ssrMockReactivity)
  return ob
}

initData最核心的的实现是observe(data),而observe(data)则是创建了一个Observer实例, 接下来我们看一下Observer类。

Observer

src/core/observer/index.ts

export class Observer {
  dep: Dep
  vmCount: number // number of vms that have this object as root $data

  constructor(public value: any, public shallow = false, public mock = false) {
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (isArray(value)) {
        // 数组的响应式处理
        for (let i = 0, l = arrayKeys.length; i < l; i++) {
            const key = arrayKeys[i]
            def(value, key, arrayMethods[key])
          }
      if (!shallow) {
        this.observeArray(value)
      }
    } else {
      // 非数组对象的响应式处理
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }

  observeArray(value: any[]) {
    for (let i = 0, l = value.length; i < l; i++) {
      observe(value[i], false, this.mock)
    }
  }
}

对于响应式数据,其内部会创建一个Dep实例,接下去,数组类型和非数组对象类型是不同处理方式

  • 数组的响应式处理看另外一篇文章->
  • 非数组对象的响应式处理

非数组对象的响应式处理

遍历对象,使用defineReactive对对象的每个属性单独处理。

export function defineReactive(
  obj: object,
  key: string,
  value?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: boolean
) {
  const dep = new Dep()
  let childOb = !shallow && observe(val, false)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      if (Dep.target) {
          dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val
      if (!hasChanged(value, newVal)) {
        return
      }
      val = newVal
      childOb = !shallow && observe(newVal, false, mock)
      dep.notify()
    }
  })

  return dep
}

defineReactive代码这里做了简化,只保存最核心的部分,可以看到每个属性都创建了一个闭包变量dep.通过Object.defineProperty的get和set函数拦截了属性的取值和赋值操作。

  • 在get函数中。通过dep.depend()将dep添加当前的Wather实例(保存在Dep.target)的依赖列表newDeps中,同时将当前Wather实例添加dep的订阅列表subs中。这里返回的value是作为一个可变的闭包变量,在set函数中可以修改这个值。

src/core/observer/dep.ts

depend() {
    if (Dep.target) {
      Dep.target.addDep(this)     
    }
}

src/core/observer/watcher.ts

  addDep(dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }
  • 在get函数中,value被最新的值覆盖,通过observe将最新的值处理成响应式数据。然后通过dep.notify遍历dep的订阅列表(Watcher列表),通知进行更新

src/core/observer/watcher.ts

notify(info?: DebuggerEventExtraInfo) {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
}

src/core/observer/watcher.ts

update() {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
}

run() {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        const oldValue = this.value
        this.value = value
        if (this.user) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(
            this.cb,
            this.vm,
            [value, oldValue],
            this.vm,
            info
          )
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

update 函数最终是调用this.cb实现更新。对于三种类型的Watcher,各自对应的是:

  • watch Watcher:watch的回调函数
  • computed Watcher: 调用生成新的computed值的回调函数
  • 渲染Watcher: 调用渲染函数,生成新的组件Dom元素