Vue - The Good Parts: reactivity响应式

avatar
@滴滴出行

前言

大家都比较清楚 Vue 中包含了一个非侵入性的响应式系统,可以说这是 Vue 的最基础最核心的一个特性了,基于这套系统,我们才实现了修改数据视图就会跟着响应进行更新,很直接、很自然,符合我们的直觉。

我们以 Vue 最新的 v2.6.14 版本来分析,响应式相关最核心的代码都在 github.com/vuejs/vue/t… 这个文件夹下。

正文分析

What

这里我们所说的响应式究竟指的是什么呢?拿 Vue 官方的文档来看,cn.vuejs.org/v2/guide/in…

image2021-6-10_13-7-30.png

其实就是能够将我们生命的普通(原始)对象数据转变为可以被视图响应的对象,使得我们可以追踪他们的变化,进而针对于这些变化自动做出响应处理。

如同上边的示例所讲的,当我们对这些值进行更新的时候,视图会进行重新渲染,即自动响应了数据的变化。

How

Vue 官方文档中,为了让大家更好的理解响应式,有专门的篇幅来讲了响应式的原理,cn.vuejs.org/v2/guide/re…

我们截取一段最核心的简述:

image2021-6-10_13-12-31.png

那在底层上,Vue 是怎么实现这样的一套机制呢,我们一起来看下源码,进行下分析。

在 Vue 初始化的过程中,会对 data props 啊等数据进行 observe 处理,也就是我们下文中要开始分析的 observe 函数。

由于涉及到的部分比较多,我们可以将整个过程分开来看,也是对应到上边图片上所展示的核心部分:

  1. 如何将一个普通对象变为一个响应式对象
  2. 如何追踪这些响应式对象的变化

先来看第一点:如何将一个普通对象变为一个响应式对象

github.com/vuejs/vue/b… 文件中,有一个暴露出来的 observe 函数:

/**
 * 为一个对象 value 创建一个 Observer 实例
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 重点就是这里,我们创建了一个 Observer 实例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

那接下来的重点就是这个 Observer 类了:

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data
 
  constructor (value: any) {
    // 我们的原始对象
    this.value = value
    // 先忽略
    this.dep = new Dep()
    this.vmCount = 0
    // 给 value 定义一个 __ob__ 属性,值指向当前的 Observer 实例
    // 这个和上边的 observe 函数中第9行的判断强相关,如果一个原始对象已经创建(关联)过了 Observer 实例,就不需要再次创建了
    def(value, '__ob__', this)
    // 数组场景 我们可以先忽略
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      // 重点看这个 针对于对象的场景
      this.walk(value)
    }
  }
 
  /**
   * 遍历原始对象,将这个对象所有的属性都利用 defineReactive 定义一次
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
 
  /**
   * 先忽略
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

看起来这个 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)
  // 最核心的 借助于 defineProperty 重新定义这个对象的属性的取值和写值行为
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 先忽略 getter
      // 我们的目标值 就是 val 的值,即 obj[key]
      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 的值!
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

其他的一些细节,我们可以先不关注,可以看出呢,我们将一个对象变为一个响应式对象,其实就是遍历这个对象的所有属性,利用 defineProperty 重新定义这个对象的属性的取值和写值行为

接下来我们看第2点:如何追踪这些响应式对象的变化

在官方文档中,也有说明,我们再来看下:

image2021-6-10_14-0-31.png

一句话总结来看:根据响应式对象Data中的 getter 来收集依赖,在更新的时候(setter行为)通知我们的依赖

真的是一个很巧妙的设计,利用 getter 和 setter 完美实现了如何收集依赖和何时通知依赖的能力。

重点回到我们刚刚大概分析了的 defineReactive 这里:

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 创建 Dep 实例
  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
      // 如果 Dep.target 存在
      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()
    }
  })
}

可以看出来,这里存在一个很重要的 Dep 需要我们仔细研究,从命名上也比较清楚,依赖。在 getter 和 setter 中,也是借助于 dep 来实现了收集(添加)依赖和依赖通知的能力。

接下来就来仔细分析下 Dep,源码地址 github.com/vuejs/vue/b…

import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
 
let uid = 0
 
/**
 * Dep 就是一个桥梁-连接器,用来连接响应式对象(观察者目标对象 Subject)和其观察者(订阅者 Watcher)
 * 当目标对象变更的时候,借助于 Dep 告诉 所有的订阅者 更新
 *
 * Dep 本身的含义,依赖,是相对于 Watcher 来讲的,因为 Watcher 中取表达式的值的时候,就是一次收集依赖的过程
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
 
  constructor () {
    this.id = uid++
    // 所有的订阅者,都是 Watcher
    this.subs = []
  }
  // 添加订阅者 watcher
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  // 移除订阅者 watcher
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
 
  depend () {
    // 订阅者添加当前依赖
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
 
  notify () {
    // 通知所有的订阅者更新
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
 
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
// 当前的目标订阅者
Dep.target = null
 
// 目标订阅者栈
// 因为订阅者之间会存在嵌套关系,所以需要一个栈来维护他们的层级关系,后边结合示例来理解,这里可以基本忽略
const targetStack = []
// 入栈 这里可以简化理解为直接设置 目标订阅者 Dep.target
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  // 设置 目标订阅者
  Dep.target = target
}
// 出栈
export function popTarget () {
  targetStack.pop()
  // 目标订阅者设置为上一次的订阅者
  Dep.target = targetStack[targetStack.length - 1]
}

Dep 的作用也是比较明确的,他和我们的响应式对象其实是1对1的关系,而和观察者也是1对多的关系,观察者都存放在 subs 中。

通过上边的分析,发现里边有一个不可或缺的部分,即订阅者 Watcher,我们接下来详细看下。

源码地址 github.com/vuejs/vue/b…

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  invokeWithErrorHandling,
  noop
} from '../util/index'
 
import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'
 
import type { SimpleSet } from '../util/index'
 
let uid = 0
 
/**
 * Watcher 观察者 就是解析表达式,收集其中的依赖 dep
 * 当表达式的值更新的时候,触发其回调 cb
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;
 
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    // ViewModel 实例,在 Vue 中,我们可以直观理解为 组件实例 或者 Vue 实例
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    // 注意这里的 deps 依赖列表
    // 保存了所有的依赖,即 Dep 实例,那么和 Dep 实例相对应的会存在一个映射的响应式对象
    // deps 和 newDeps 都是,先把他俩同等对待
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    // 调用 get 获取(表达式的)初始值
    this.value = this.lazy
      ? undefined
      : this.get()
  }
 
  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // 注意这里调用 pushTarget,约等于设置好了 Dep 中的目标观察者对象 watcher
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 这里一个核心点,相当于执行了我们的表达式
      // 里边就会读取响应式对象的值,也就是会触发其 getter
      // 即 在 defineReactive 中,利用 defineProperty 定义好的属性 getter
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      // 执行完毕,Dep 中的目标观察者对象为上一次的观察者对象 watcher 简单场景目前可以认为 Dep.target = null
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
 
  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      // 给依赖列表加上依赖 dep
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // 同样的,依赖也需要添加当前 watcher 为订阅者,约等于这个 watcher 在观测这个 dep 映射的响应式对象
        dep.addSub(this)
      }
    }
  }
 
  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }
 
  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    // 当依赖dep通知更新的时候,被调用了!
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      // 同步情况下 直接 run
      // 我们先把关注点放在这里即可
      this.run()
    } else {
      queueWatcher(this)
    }
  }
 
  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      // 因为依赖的响应式对象的值更新了,所以需要重新计算表达式 获取表达式新的值
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // 值发生了变化
        // set new value
        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 {
          // 触发回调 cb,传入新值和旧值
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
 
  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }
 
  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
 
  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

名字上也比较容易理解,观察者 Watcher。如果单纯的从Watcher来看,他和Dep之间其实是一个1对多的关系,但是我们在分析 Dep 的时候也得出了Dep 和 Watcher 是1对多的关系,所以这里可以进一步得出结论,在复杂一点的场景,他们之间其实是一个多对多的关系。

这种多对多的关系,可以这样举例理解:

  • 我们有两个响应式对象 o1 和 o2
    • 对象 o1,相对应的存在一个依赖 Dep 实例 dep1
    • 对象 o2,相对应的存在一个依赖 Dep 实例 dep2
  • 有两个观察者 w1 和 w2
    • w1,观察的表达式是 o1 + o2
    • w2,观察的表达式是 o2

那么,正确的理解顺序就是从 Watcher 实例化开始,我们以 w1 初始化举例:

  1. 初始化会执行表达式
    • get 中会 pushTarget,即设定了当前 Dep.target 为 w1
    • 表达式执行,过程中包含了获取 o1 和 o2 的值的操作
  2. 获取 o1 和 o2 值,也就是会各自触发他们在 defineReactive 中的 getter,进而调用了他们各自对应的 dep 的 depend 方法
  3. dep 的 depend 会调用 Dep.target.addDep,即 w1.addDep 操作
    • w1.newDeps 会增加上 dep1(对应o1) 和 dep2(对应o2)
    • dep1和dep2的订阅者列表subs中也会增加订阅者,即观察者w1

这样对于 w1 而言,w1的依赖项包含了 dep1 和 dep2,而 dep1 和 dep2 的 subs 中都包含 w1。

此时,把 w2 考虑进来,w2的依赖项包含了 dep2,而 dep2 的 subs 中也包含了 w2。

如果后续o2的值发生了更新,那么就会借助于 dep2 通知其所有的 subs:w1和w2 进行更新 update。但是如果是o1发生了更新,那么就会借助于 dep1 通知其所有的subs: w1 进行更新 update。

相信通过这个例子,你已经基本理解了整个追踪响应式对象的变化的过程。可能看上述完整的代码还是有一些吃力,这里我们也对实现进行一个简化,方便你理解:

class Dep {
  static target = null
 
  constructor () {
    this.subs = []
  }
  addSub (sub) {
    this.subs.push(sub)
  }
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
 
class Watcher {
  constructor (getter, cb) {
    this.cb = cb
    this.deps = []
    this.getter = getter
    this.value = this.get()
  }
  get () {
    Dep.target = this
    let value
    try {
      value = this.getter()
    } catch (e) {
      // catch
    } finally {
      Dep.target = null
    }
    return value
  }
  addDep (dep) {
    this.deps.push(dep)
    dep.addSub(this)
  }
  update () {
    this.run()
  }
  run () {
    const value = this.get()
    if (value !== this.value) {
      const oldValue = this.value
      this.value = value
      this.cb(value, oldValue)
    }
  }
}
 
function observe (value) {
  const ob = new Observer(value)
  return ob
}
 
class Observer {
  constructor (value) {
    this.value = value
    this.walk(value)
  }
  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
}
 
function defineReactive (obj, key, val) {
  const dep = new Dep()
  if (arguments.length === 2) {
    val = obj[key]
  }
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      if (Dep.target) {
        dep.depend()
      }
      return val
    },
    set: function reactiveSetter (newVal) {
      val = newVal
      dep.notify()
    }
  })
}

如果我们使用的话,会按照如下方式:

const o1 = observe({value: 'o1'})
const o2 = observe({value: 'o2'})
 
const w1 = new Watcher(() => {
  return o1.value.value + o2.value.value
}, (val, oldVal) => {
  console.log('w1 watch value changed:', val, oldVal)
})
const w2 = new Watcher(() => {
  return o2.value.value
}, (val, oldVal) => {
  console.log('w2 watch value changed:', val, oldVal)
})
 
o1.value.value = 'o1o1'
// log:
//      w1 watch value changed: o1o1o2 o1o2
o2.value.value = 'o2o2'
// log:
//      w1 watch value changed: o1o1o2o2 o1o1o2
//      w2 watch value changed: o2o2 o2

实现会有一些不一致的地方,这里简化处理,也可以看出我们上文中的有些描述是不精确的,例如 o1 对应的 Dep 实例 dep1,其实我们这里真正的对应关系是 o1 这个对象的属性 value 所对应的 Dep 实例才是我们想要的 dep1。

更多的是想要方便大家理解,正确认识他们。

Why

那 Vue 为何做了一套响应式系统呢?不做可不可以。要想回答好这个问题,估计只有 Vue 作者尤大自己了。但是还是可以从一些方面做一些简单的猜测。

大概是在10年左右,MVX框架在渐渐出现,基本是将其他领域的一些优秀的思想(主要还是来源自GUI,PC类的应用开发)和设计融入到前端领域,所以陆陆续续出来了很多的框架,比较出名的可能会有一些:Backbone、Knockout.js、angular.js、ember 等等,也出现了著名的帮你做选择的经典的 TodoMVC 项目。

在MVVM(非严格意义)框架中,一个核心思想就是双向绑定,意味着视图和View之间可以自动做到同步。这个过程中,受限于当时的兼容性要求,在JS中还不能很好的支持 Object.defineProperty,所以当年如 angular.js 这样超级火爆的新星,所用的就是脏检查的逻辑,来确定用户到底更新了哪些数据,依次来去做到数据变更了自动更新视图。而另一个框架 Knockout.js 则是需要用户显式的调用 get 和 set 这样的API来达到通知的目的,进而去更新视图。

尤大当时在Google,也是非常喜欢 Angular.js,但是不够轻量,而且性能也不好,开发者的上手成本也比较高,所以他就吸收了Angular.js中比较精华的部分,创建出了Vue,直接放弃掉了对于陈旧浏览器的支持,直接使用原生的Object.defineProperty实现了对数据的侦测,即核心的非侵入式的响应式系统,也不需要像 Knockout.js 那样显式调用一些读写操作。

有了这套响应式系统,在Vue中,直接操作数据去影响视图,而且非常直观,并且在这个基础上,Vue还提供了watch和computed这样非常好用的响应式相关的特性。

总结

我们上边已经分析了最核心的整个响应式系统,当然,其实还是有很多的设计细节,我们这里并没有额外的去讲,并不是说这些逻辑和细节不重要,而是我们把最核心的部分拿出来帮助大家理解Vue的reactivity响应式系统,所以基本只是考虑了最简单的case。

那从中我们可以学到些什么呢?

目录文件拆分

响应式相关最核心的代码都在 github.com/vuejs/vue/t… 这个文件夹下,我们可以看到下面对应的文件结构:

image2021-6-10_22-54-32.png

index.js 是入口文件,也就是我们上边最早分析 observe 函数定义的地方;剩下的根据各个模块的职责不同,做了很好的拆分,每个模块一个文件。

当然,在整个Vue中,目录的划分和文件模块拆分还是很合理和清楚的,很值得我们完备的去学习,整个可以单独一篇整理。

整体模式-观察者

可以看到我们上边一直在说观察者、订阅者这些名词,那这个和我们传统认知的设计模式之一——观察者模式——是能够对应起来的吗?答案可能是Yes,也可以是No。

观察者模式,参考维基百科 zh.wikipedia.org/zh-hans/%E8…

Yes的原因是,他基本符合了观察者模式中的要素的定义:观察者目标对象 Subject — 响应式对象,也有观察者 Watcher。当目标对象发生变更的时候,去通知观察者更新。这样的一套逻辑在这里是很完备的。

No的原因呢,按照严格定义,观察者模式是一种一对多的关系,即一个目标对象对应多个观察者,从这个点出发,经过我们前边的分析,我们知道 Dep 和 Watcher 之间是多对多的关系,且严格意义上讲,观察者的目标对象应该是我们的响应式对象才对,而不是 Dep,但是 Dep 和响应式对象是1对1的关系。

所以,这里个人更倾向于是观察者模式思想的灵活运用,当然,这个也是体现出了作者自身充分理解了观察者模式,并且做到了活学活用的程度,很完美的解决了依赖收集的问题。

Respect!整篇来看 Vue 中的话,其实还有很多模式的运用,都是很好的范例,值得我们去学习,更值得我们去思考&深刻理解,让我们自身也可以做到活学活用。

世界上本没有模式,”走“的人多了,就有了模式 O(∩_∩)O

在 dep.js 中,最底部 pushTarget 和 popTarget,我们其实看到了对于栈的运用(这里同样用数组模拟),在一些树状嵌套或者递归场景中,栈这种数据结构能够帮助我们很好的解决问题。

虽然很基础,但是却很好用,你可以想想,自己曾经在什么地方有用到栈吗?是在什么场景下、要解决什么问题?

JS单线程

相对应的其实就是 Dep.target 这里的运用,十分的巧妙,进行了很好的解耦。核心的原因就是因为 JS 是单线程的,在执行的时候,一定不会出现两个 watcher 的逻辑同时在执行。

我们日常之中,也是可以很灵活运用这个优势的。甚至于说,你可以发现,在 Vue 3 中,这种技巧依旧是在使用,且使用的更多了。

不要说 Worker 线程😯

防重处理

在 observe 函数中,针对于目标原始对象,为了避免重复对其做逻辑的处理,我们保存了一个私有属性 ob,依次来判断是否是已经进行了响应式相关的逻辑处理。本质上来讲,这就是一种缓存策略。

有很多时候,在我们实际的业务场景中,也会存在这种情况,但可能我们都忽略了,可能进行了重复处理。尤其是遇到一些耗时的计算或者频繁的处理,我们是可以考虑加上缓存策略,避免重复进行逻辑计算。

其他小Tips

  • 再次的各种错误处理,以及开发者日志
  • 再次出现的数组循环处理,复制,倒叙等
  • 数组的patch处理,以及这样做的目的是啥?
  • Watcher 中对于 deep 观测情况的处理,github.com/vuejs/vue/b… traverse 是如何处理的
  • 队列 queueWatcher 实现

滴滴前端技术团队的团队号已经上线,我们也同步了一定的招聘信息,我们也会持续增加更多职位,有兴趣的同学可以一起聊聊。