Чтение исходного кода Vue — принцип сбора зависимостей

исходный код Vue.js

Vue уже составляет треть текущего отечественного веб-терминала, а также является одним из моих основных технологических стеков. Я знаю его в повседневном использовании, и мне это любопытно. Кроме того, большое количество исходного кода Vue Недавно в сообществе появились классы чтения. В статье я воспользовался этой возможностью, чтобы почерпнуть немного пищи из всех статей и обсуждений. В то же время я обобщил некоторые идеи при чтении исходного кода и подготовил несколько статей в качестве результатов своей работы. собственное мышление.мой уровень ограничен.добро пожаловать, чтобы оставить сообщение для обсуждения~

Целевая версия Vue:2.5.17-beta.0

Комментарии к исходному коду Vue:GitHub.com/Шерлок Эд9…

Отказ от ответственности: Синтаксис исходного кода в статье использует Flow, и исходный код сокращен по мере необходимости (чтобы не путать @_@), если вы хотите увидеть полную версию, пожалуйста, введите вышегитхаб-адрес, эта статья представляет собой серию статей, адрес статьи внизу ~

Заинтересованные студенты могут добавить группу WeChat в конце статьи для совместного обсуждения~

1. Отзывчивая система

Благодаря представлению на официальном веб-сайте мы знаем, что Vue.js — это инфраструктура MVVM, она заботится не об изменениях представлений, а об обновлениях представлений, управляемых данными, что делает наше управление состоянием очень простым и как этого добиться. Украдена картинка с официального сайта

Каждый экземпляр компонента имеет соответствующийWatcherЭкземпляр объекта, который будет записывать свойства как зависимости во время рендеринга компонента, а затем использовать их как зависимостиsetterПри вызове он уведомитwatcherПересчитать, что приведет к обновлению связанных с ним компонентов.

Здесь есть три важных понятияObserve,Dep,Watcherсоответственно находится вsrc/core/observer/index.js,src/core/observer/dep.js,src/core/observer/watcher.js

  • ObserveКласс в основном добавляется в свойства отзывчивого объектаgetter/setterИспользуется для сбора зависимостей и распространения обновлений.
  • DepКласс, используемый для сбора зависимостей текущего реактивного объекта.
  • WatcherКласс является наблюдателем, а экземпляры делятся на три типа: наблюдатель рендеринга, наблюдатель вычисляемых свойств и наблюдатель слушателя.

2. Реализация кода

2.1 initState

Отвечающая запись находится в src/core/instance/init.js.initStateсередина:

// src/core/instance/state.js

export function initState(vm: Component) {
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)              // 初始化props
  if (opts.methods) initMethods(vm, opts.methods)        // 初始化methods
  if (opts.data) initData(vm)                            // 初始化data
  if (opts.computed) initComputed(vm, opts.computed)     // 初始化computed
  if (opts.watch) initWatch(vm, opts.watch)              // 初始化watch
  }
}

Он очень регулярно определяет несколько методов для инициализацииprops,methods,data,computed,wathcer, посмотри сюдаinitDataметод, взгляните на леопарда

// src/core/instance/state.js

function initData(vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
                    ? getData(data, vm)
                    : data || {}
  observe(data, true /* asRootData */)             // 给data做响应式处理
}

Сначала оцените, являются ли данные функцией, если да, возьмите возвращаемое значение, если нет, затем возьмите сами себя, а затемobserveпара методовdataДля обработки этот метод пытается создать экземпляр Observer.__ob__, возвращает новый экземпляр Observer, если он успешно создан, или возвращает существующий экземпляр Observer, если экземпляр Observer уже существует.

2.2 Observer/defineReactive

// src/core/observer/index.js

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

Этот метод в основном используетdataВ качестве параметра для создания экземпляра объекта Observer Observer представляет собой класс, используемый для сбора зависимостей иnotifyОбновление, конструктор Observer используетdefineReactiveметод для реактивного форматирования ключей объекта и рекурсивного добавления к свойствам объектаgetter/setter, срабатывает, когда данные оцениваютсяgetterИ собирать зависимости, запускать сначала при изменении значенияgetterперезапускатьsetterи отправлять обновления

// src/core/observer/index.js

export class Observer {
  value: any;
  dep: Dep;

  constructor (value: any) {
    value: any;
    this.dep = new Dep()
    def(value, '__ob__', this)    // def方法保证不可枚举
    this.walk(value)
  }

  // 遍历对象的每一个属性并将它们转换为getter/setter
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) { // 把所有可遍历的对象响应式化
      defineReactive(obj, keys[i])
    }
  }
}

export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {
  const dep = new Dep()         // 在每个响应式键值的闭包中定义一个dep对象

  // 如果之前该对象已经预设了getter/setter则将其缓存,新定义的getter/setter中会将其执行
  const getter = property && property.get
  const setter = property && property.set

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val         // 如果原本对象拥有getter方法则执行
      if (Dep.target) {                    // 如果当前有watcher在读取当前值
        dep.depend()                       // 那么进行依赖收集,dep.addSub
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val    // 先getter
      if (newVal === value || (newVal !== newVal && value !== value)) {   // 如果跟原来值一样则不管
        return
      }
      if (setter) { setter.call(obj, newVal) }         // 如果原本对象拥有setter方法则执行
      else { val = newVal }
      dep.notify()                                     // 如果发生变更,则通知更新,调用watcher.update()
    }
  })
}

getterПри сборе зависимостей обратите внимание на это, только когдаDep.targetСбор зависимостей будет выполняться только при наличии значения, этоDep.targetнаходится в экземпляре Watchergetкогда вызывается методpushTargetподтолкнет наблюдателя с текущим значением вDep.target, исходный наблюдатель помещается в стекtargetStackВ стеке значение наблюдателя текущего значения извлекается из стека после того, как значение наблюдателя завершено, и исходное значение наблюдателя присваиваетсяDep.target,cleanupDepsНаконец-то поставил новыйnewDepsНет наблюдателя, который не был бы очищен, чтобы предотвратить бесполезные триггеры наблюдателя в представлении.

setterкогда первыйgetter, и если в старом значении нет изменений, вернуть, если есть изменение, dep уведомляет все экземпляры Watcher, хранящиеся в подпрограммах, которые полагаются на эти данные.updateдля обновления, здесьupdateЦКqueueWatcher( )Асинхронно отправить в очередь наблюдателя планировщикаqueueв, в nextTickflushSchedulerQueue( )Выньте наблюдателя из очереди и выполните егоwatcher.runИ выполните соответствующую функцию ловушки

2.3 Dep

Ключевое слово, упомянутое много раз вышеDep, который является контейнером для сбора зависимостей, илиСборщик зависимостей, он записал, какие Наблюдатели зависели от своих собственных изменений или какие Наблюдатели подписались на свои собственные изменения; вот цитата пользователя сети:

@liuhongyi0101: Проще говоря, это подсчет ссылок.Кто займет мои деньги, я запишу этого человека.Если моих денег меньше, я сообщу им, что у меня нет денег.

А вот маленькая книга, в которой записаны люди, которые заняли деньгиDepсабвуферы в экземпляре

// src/core/observer/dep.js

let uid = 0            // Dep实例的id,为了方便去重

export default class Dep {
  static target: ?Watcher           // 当前是谁在进行依赖的收集
  id: number
  subs: Array<Watcher>              // 观察者集合
  
  constructor() {
    this.id = uid++                             // Dep实例的id,为了方便去重
    this.subs = []                              // 存储收集器中需要通知的Watcher
  }

  addSub(sub: Watcher) { ... }  /* 添加一个观察者对象 */
  removeSub(sub: Watcher) { ... }  /* 移除一个观察者对象 */
  depend() { ... }  /* 依赖收集,当存在Dep.target的时候把自己添加观察者的依赖中 */
  notify() { ... }  /* 通知所有订阅者 */
}

const targetStack = []           // watcher栈

export function pushTarget(_target: ?Watcher) { ... }  /* 将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中 */
export function popTarget() { ... }  /* 将观察者实例从target栈中取出并设置给Dep.target */

здесьDepв случаеsubsСобранная зависимость — это watcher, т.е.WatcherЭкземпляр , используемый для уведомления об обновлениях в будущем

2.4 Watcher

// src/core/observer/watcher.js

/* 一个解析表达式,进行依赖收集的观察者,同时在表达式数据变更时触发回调函数。它被用于$watch api以及指令 */
export default class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean      // 是否是渲染watcher的标志位
  ) {
    this.getter = expOrFn                // 在get方法中执行
    if (this.computed) {                   // 是否是 计算属性
      this.value = undefined
      this.dep = new Dep()                 // 计算属性创建过程中并未求值
    } else {                               // 不是计算属性会立刻求值
      this.value = this.get()
    }
  }

  /* 获得getter的值并且重新进行依赖收集 */
  get() {
    pushTarget(this)                // 设置Dep.target = this
    let value
    value = this.getter.call(vm, vm)
    popTarget()                      // 将观察者实例从target栈中取出并设置给Dep.target
    this.cleanupDeps()
    return value
  }

  addDep(dep: Dep) { ... }  /* 添加一个依赖关系到Deps集合中 */
  cleanupDeps() { ... }  /* 清理newDeps里没有的无用watcher依赖 */
  update() { ... }  /* 调度者接口,当依赖发生改变的时候进行回调 */
  run() { ... }  /* 调度者工作接口,将被调度者回调 */
  getAndInvoke(cb: Function) { ... }
  evaluate() { ... }  /* 收集该watcher的所有deps依赖 */
  depend() { ... }  /* 收集该watcher的所有deps依赖,只有计算属性使用 */
  teardown() { ... }  /* 将自身从所有依赖收集订阅列表删除 */
}

getвыполняется в методеgetterОн передается, когда наблюдатель заново отображается в началеupdateComponent = () => { vm._update(vm._render(), hydrating) }, этот метод первыйvm._render()Сгенерируйте и визуализируйте дерево VNode, в процессе завершите текущий экземпляр Vue.vmдоступ к данным наgetter,Потомvm._update()идтиpatch

Обратите внимание здесьgetМетод наконец выполняетсяgetAndInvoke, этот метод сначала просматривает сохраненные данные в наблюдателеdeps, УдалитьnewDepподписки, которые больше не существуют вdepIds = newDepIds; deps = newDeps,ПучокnewDepIdsа такжеnewDepsПустой. Удаляйте старые подписки, которые больше не нужны после добавления новых подписок, чтобы в некоторых случаях, напримерv-ifНаблюдатель не будет уведомлен об изменении данных, для которых шаблон больше не требуется.updateохватывать

2.5 Резюме

Весь процесс сбора примерно такой, вы можете взглянуть на вышеприведенный процесс

Watcher имеет следующие сценарии использования:

  • render watcherНаблюдатель рендеринга, наблюдатель, используемый для рендеринга представления.
  • computed watcherНаблюдатель за вычисляемыми свойствами, поскольку вычисляемые свойства зависят от других, а также зависят от них, поэтому они также содержатDepпример
  • watch watcherслушатель наблюдатель

Пока другие наблюдатели (watchers) зависимости, такие как данные, свойства данных, вычисляемые свойства, реквизиты, будут генерировать экземпляр Dep в замыканииdepи называетсяgetterкогдаdep.dependСоберите, кто от него зависит, и сохраните зависимого наблюдателя в своих собственных подпрограммах.this.subs.push(sub), чтобы уведомить, когда он изменитсяnotifyвставитьdep.subsЗависит от себя в массивеwatchersЯ изменился, пожалуйста, будь вовремяupdate ~

Всякий раз, когда объект зависит от других реактивных объектов, будет сгенерирован наблюдательwatcher, считать этоwatcherОт каких отзывчивых объектов зависит в этомwatcherПрежде чем оценивать текущиеwatcherустановить глобальныйDep.target, и во время, когда отзывчивый объект зависит от измененийupdate


Эта статья представляет собой серию статей, и более поздние части будут обновлены позже, чтобы добиться прогресса вместе ~

  1. Чтение исходного кода Vue — файловая структура и механизм работы
  2. Чтение исходного кода Vue — принцип сбора зависимостей
  3. Чтение исходного кода Vue — пакетное асинхронное обновление и принцип nextTick

Большинство сообщений в Интернете имеют разную глубину и даже некоторые несоответствия. Следующие статьи являются кратким изложением процесса обучения. Если вы найдете какие-либо ошибки, пожалуйста, оставьте сообщение, чтобы указать ~

Ссылаться на:

  1. Изучение исходного кода Vue2.1.7
  2. Демистификация технологии Vue.js
  3. Анализ внутренней работы Vue.js
  4. Документация Vue.js
  5. [Большие галантереи] Взявшись за руки, я познакомлю вас с исходным кодом vue.
  6. MDN - Object.defineProperty()
  7. Изучение исходного кода Vue.js — параметр данных Изучение состояния

PS: Всех приглашаю обратить внимание на мой публичный аккаунт [Front End Afternoon Tea], давайте работать вместе~

Кроме того, вы можете присоединиться к группе WeChat «Front-end Afternoon Tea Exchange Group», нажмите и удерживайте, чтобы определить QR-код ниже, чтобы добавить меня в друзья, обратите вниманиеДобавить группу, я заберу тебя в группу~