Vue - The Good Parts: reactivity响应式

前言

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

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

正文分析

What

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

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

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

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

How

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

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

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 实现

滴滴前端技术团队的团队号已经上线,咱们也同步了必定的招聘信息,咱们也会持续增长更多职位,有兴趣的同窗能够一块儿聊聊。

相关文章
相关标签/搜索