Интерпретация исходного кода Vue
Версия:vue@2.6.11Предисловие: эта статья представляет собой исключительно личное распределение очков знаний, а не общий оригинал. Некоторые вопросы и ответы взяты из других статей и интегрированы в их собственное понимание, и, наконец, создается эта статья.
До этого я время от времени читал исходный код, связанный с Vue, например, видел некоторые интервью: «Каков принцип nextTick?» Когда дело доходит до хитрой ошибки (задерживает меня от работы), я должен найти информацию или прочитать соответствующие исходники и т.д. и т.п. Вы все похожи на меня? (скажите да, пожалуйста)
Я знаю, поэтому очки знаний, полученные на основе этой ситуации, слишком фрагментарны.
Есть ли что-то, что может связать воедино то, что мы узнали? Конечно!
Этот ю составил простую ментальную карту для чтения исходного кода Vue, которая может помочь вам понять механизм всей операции Vue, а также облегчить обзор знаний. напрямую~~
Не много ерунды, начнем с галантереи:
Нажмите, чтобы свободно масштабироватьVue Source Code Mind Map
Все началось, надо начать с......
Ну, изnew Vue()
давай поговорим
Жизненный цикл
Должен ли я возиться здесь? ? ?
Каковы жизненные циклы компонентов Vue?
-
beforeCreate
После инициализации экземпляра перед вызовом вызывается наблюдатель данных и конфигурация события/наблюдателя.
-
created
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。 Однако этап монтирования еще не начался,
$el
свойство в настоящее время недоступно. -
beforeMount
Вызывается перед началом монтирования: актуально
render
Функция вызывается впервые.Этот хук не вызывается во время рендеринга на стороне сервера. -
mounted
Вызывается после монтирования экземпляра, затем
el
Вновь созданныйvm.$el
Заменены.如果根实例挂载到了一个文档内的元素上,当mounted
когда звонятvm.$el
Также в документации. Уведомлениеmounted
Не будетУбедитесь, что все подкомпоненты также смонтированы вместе. Если вы хотите дождаться рендеринга всего представления, вы можетеmounted
внутреннее использованиеvm.$nextTick:Этот хук не вызывается во время рендеринга на стороне сервера. -
beforeUpdate
Вызывается, когда обновление данных происходит до исправления виртуального DOM. Здесь для доступа к существующему DOM перед обновлением, например, добавлено ручное удаление прослушивателей событий.Этот хук не вызывается во время рендеринга на стороне сервера, потому что на стороне сервера будет выполняться только первый рендеринг.
-
updated
Этот хук вызывается после повторного рендеринга виртуального DOM и исправления из-за изменений данных.Этот хук не вызывается во время рендеринга на стороне сервера.
-
activated
Вызывается при активации компонента, закэшированного функцией проверки активности.Этот хук не вызывается во время рендеринга на стороне сервера.
-
deactivated
Исходный текст официального веб-сайта: вызывается, когда компонент, закэшированный keep-alive, отключен.Этот хук не вызывается во время рендеринга на стороне сервера.
-
beforeDestroy
Вызывается перед уничтожением экземпляра. На этом этапе экземпляр все еще полностью доступен.Этот хук не вызывается во время рендеринга на стороне сервера.
-
destroyed
Вызывается после уничтожения экземпляра. После вызова хука все директивы, соответствующие экземпляру Vue, отвязываются, все прослушиватели событий удаляются, а все дочерние экземпляры уничтожаются.Этот хук не вызывается во время рендеринга на стороне сервера.
Каков порядок вызовов жизненного цикла компонентов в Vue?
-
Порядок вызова компонентов — сначала родительский, затем дочерний, а порядок рендеринга — сначала дочерний, затем родительский.
-
Операция уничтожения компонента — это сначала родительский компонент, затем дочерний, а порядок уничтожения — сначала дочерний, а затем родительский.
проиллюстрировать:
Вот родительский компонентparent
и 2 подкомпонентаchild1
,child2
, child1 имеет дочерние компонентыchild1child
Тогда порядок цикла объявления в это время должен быть:
Компоненты вызываются в порядке сначала родительский, затем дочерний, почему?
Мне нечего сказать об этом, вы хотите сначала поставить сына, а потом отца?
Порядок, в котором выполняется рендеринг, сначала дочерний, а затем родительский, почему?
insertedVnodeQueue
Вставленная очередь виртуальных узлов
function patch(vnode) {
// 1.虚拟节点队列
const insertedVnodeQueue = [];
// 2.创建新节点,具体查看下面函数的具体内容
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// 3.清空insertedVnodeQueue队列
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
}
/** 创建DOM元素,并且append到父元素 */
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
// 1.创建了DOM
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
// 2.递归创建子vnode的DOM
createChildren(vnode, children, insertedVnodeQueue)
// 重点!!!
// 3.递归创建好了子vnode,才把自己的vnode推到虚拟节点队列,此时,父虚拟节点在子虚拟节点后面
if (isDef(data)) {
// 实质:insertedVnodeQueue.push(vnode)
invokeCreateHooks(vnode, insertedVnodeQueue)
}
/** DOM操作了 将生成的DOM append到target DOM(parentVnode.elm) */
insert(parentElm, vnode.elm, refElm)
}
/** 将DOM append到父元素 */
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
// 清空insertedVnodeQueue队列
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
Здесь еще раз объясните логику приведенного выше кода:
-
В первом патче при создании нового узла vnode он сначала определит
insertedVnodeQueue
Вставленная очередь виртуальных узлов тогда позвониcreateElm
Элемент DOM создается, и vnode собирается вinsertedVnodeQueue
очередьВ каком порядке они были собраны?
пожалуйста, проверьте
createEle
Второй и третий пункты функции можно найти в том, что если есть дочерний vnode, сначала будет завершена генерация DOM для создания дочернего vnode (т.е. приоритетное монтирование), и, наконец, будет смонтирован родительский vnode. Следовательно, если три компонента родитель, дочерний элемент и внук имеют следующие отношения:-- parent
-- child
-- внук
Затем первый шаг будет генерировать DOM родителя, второй шаг будет рекурсивно генерировать DOM дочернего элемента и так далее, чтобы генерировать DOM внука.
Компонент внука не имеет дочерних узлов, тогда внук vnode будет добавлен к компоненту.
insertedVnodeQueue
Очередь, за которым следует ребенок, родитель, в это времяinsertedVnodeQueue
Это [внуконь, детство, родитель, ParentVnode]Затем родитель вставляет полученный DOM под целевой элемент. Конец последнего патча называется
invokeInsertHook
, в основном для очистки очереди вставленного VnodeQueueподвести итог:Порядок, в котором выполняется рендеринг, сначала дочерний, затем родительский.
Операция уничтожения компонента — это сначала родительский компонент, затем дочерний, а порядок уничтожения — сначала дочерний, а затем родительский.
Чтобы уничтожить компонент, нужно вызвать метод destroy экземпляра компонента.vm.$destroy()
Vue.prototype.$destroy = function () {
const vm: Component = this
// 开始销毁组件
callHook(vm, 'beforeDestroy')
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// 销毁完成
callHook(vm, 'destroyed')
}
Метод уничтожения называется первымbeforeDestroy
крюк
тогда позвониpatch
функция для уничтожения vnode
Наконец, вызовите хук завершения уничтоженияdestroyed
Затем позвонитеpatch
Каков конкретный процесс уничтожения vnode по функции?
Первый взгляд наpatch
функция
function patch() {
/** if -> 销毁: 如果 newVnode传了`null`和oldVnode有传 说明:销毁oldVnode节点 */
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
}
Таким образом, функция исправления уничтожает vnode, который в основном называетсяinvokeDestroyHook
Тогда смотрите нижеinvokeDestroyHook
Чем вы в основном занимаетесь?
/**
* 销毁vnode和子vnode
* */
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
/**
* 销毁vnode
**/
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
}
/**
* 递归子节点vnode,进行销毁操作
**/
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
Из того, что видно выше,invokeDestroyHook
Функция сначала вызывает ловушку уничтожения текущего vnode,
vnode destory hook
componentVNodeHooks = {
destroy (vnode: MountedComponentVNode) {
const { componentInstance } = vnode
if (!componentInstance._isDestroyed) {
// 调用的是实例的销毁方法
componentInstance.$destroy()
}
}
}
Затем в рекурсивном дочернем узле vnode экземпляры уничтожаются один за другим.Но родительский vnode будет вызываться только после уничтожения дочернего vnode.destoryed
крюк,
Следовательно, операция уничтожения компонента — это сначала родительский компонент, затем дочерний, а порядок завершения уничтожения — сначала дочерний, а затем родительский.
На каком этапе можно получить доступ к манипуляционному DOM?
существуетmounted
На этом этапе вы можете получить доступ к DOM и управлять им.
проиллюстрировать:
В реализации компонента mount$mount
Когда метод, он будет вызыватьсяbeforeMount
Хук, DOM не был сгенерирован в это время
Затем начните готовиться к созданию DOM:
- воплощать в жизнь
_render
метод, генерировать vnode - воплощать в жизнь
__patch__
Метод создает DOM и вставляется в родительский элемент (в частности, обратитесь к другим:Виртуальный DOM и различиясерединаpatch
), DOM монтируется
передачаmounted
крюк.
В какой жизненный цикл обычно помещается ваш интерфейсный запрос?
Его можно использовать в create, beforeMount и смонтировать.
Описание: поскольку данные были созданы в этих трех функциях ловушек, данные, возвращаемые сервером, могут быть назначены.
Но я привык использоватьcreated
, по следующим причинам:
- Получите данные с сервера как можно раньше
-
beforeMount
,mounted
не вызывается во время рендеринга на стороне сервера,created
Он будет вызываться, поэтому размещение его в файле created способствует согласованности.
Продолжайте спрашивать: тогда почему бы не использоватьbeforeCreate
Хук, запрос интерфейса асинхронный, это не должно влиять на присвоение возвращаемых сервером данных?
Ответ: Если это просто запрос интерфейса, я думаю, что в том, что вы сказали, нет ничего плохого;
Но конкретная ситуация не так проста как вы сказали.Перед запросом интерфейса часто необходимо получить или инициализировать какие-то данные.Данный процесс может потребовать операции данных данных.Исходя из этого я все же чувствую что использованиеbeforeCreate
Крюк недостаточно стабилен.
Система реагирования на основе модели публикации-подписки
Что такое модель публикации-подписки? видимыйКарта разума исходного кода VueКарта разума режима публикации-подписки в левом нижнем углу
Почему данные в компоненте являются функцией?
В официальной документации: тип данных
Object
|Function
, Но дано ограничение: определение компонента принимает толькоfunction
.
Мой простой ответ: это не объект, возвращаемый функцией, а несколько экземпляров одного и того же компонента совместно используют объект данных, и будут скрытые опасности ссылочных типов.
Официальный ответ: когдакомпонентыопределено,
data
Должна быть объявлена как функция, возвращающая исходный объект данных, поскольку компонент может использоваться для создания нескольких экземпляров. еслиdata
Все еще является чистым объектом, все экземпляры будутобщая ссылкаТот же объект данных! путем предоставленияdata
функция, каждый раз, когда создается новый экземпляр, мы можем вызватьdata
функция, которая возвращает новую копию объекта данных исходных данных.При необходимости это можно сделать через
vm.$data
входящийJSON.parse(JSON.stringify(...))
Получите глубокую копию исходного объекта данных.
Расскажите о принципе двусторонней привязки данных Vue.
согласно сКарта разума исходного кода VueНайдено в: Реагирование данных Vue происходит в
beforeCreate
а такжеcreated
между
Отзывчивые принципы
Основной принцип основан на API es5:Object.defineProperty
Пусть свойство данных принадлежитgetter
&setter
, тем самым реагируя
function initData (vm: Component) {
let data = vm.$options.data
// 1.代理每个属性到实例vm上
// proxy data on instance
const keys = Object.keys(data)
let i = keys.length
while (i--) {
proxy(vm, `_data`, key)
}
// 观察数据data
// observe data
observe(data, true /* asRootData */)
}
export function observe (value: any) {
Object.keys(value).forEach((key) => defineReactive(value, key, value[key]))
}
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 获取到data的属性的描述器,只有可配置性的才能操作修改
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]
}
// 重新定义属性,让属性拥有`getter` & `setter`
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
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
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
}
})
}
Коллекция зависимостей
Каков процесс сбора зависимостей? Кто кого собирает? Как его собирают? Какова цель сбора?
Когда у меня в голове возникает так много вопросов, я буду использовать эти вопросы, чтобы найти ответы один за другим, поехали!
Все это должно исходить изapp.$mount('#app')
Говоря о:
Зачем? Подписывайтесь на меня.
app.$mount('#app')
Я, вероятно, сделал упрощенную версию этой операции, и в последующем исходном коде будут выбраны только ключевые моменты для отображения.
Vue.prototype.$mount = function (el) {
const vm = this;
// ...
callHook(vm, 'beforeMount')
// ...
// 核心点:在这创建了一个render `Watcher`
new Watcher(vm, () => {
// 1.调用渲染函数返回一个虚拟节点
const vnode = vm._render();
// 2.调用patch生成真实的DOM
// 渲染真实DOM,就会使用到数据进行模版填充
// 使用到数据就会被监听器`Observer`给监听到
// 由此触发了数据data的property的`getter`
vm.__patch__(vm.$el, vnode);
})
// ...
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
// ...
}
Слушатель Наблюдатель
упомянутый вышеObserver
,Что это? Почему вы не упомянули об этом раньше?
Ну.... создание Обсервера должно быть вышеОтзывчивые принципысерединаobserve
метод создан, вот добавить:
export function observe (value: any, asRootData: ?boolean): Observer | void {
let ob: Observer | void
// ...
// 在这里就创建了监听器
ob = new Observer(value)
// ...
return ob
}
Давайте посмотрим на слушателяObserver
Добрый:
export class Observer {
value: any;
constructor (value: any) {
this.value = value
// 1.给对象定一个监听器实例
def(value, '__ob__', this)
// 2.对数据进行深度观察
// 这里对数据的一些原型方法进行了复写
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
тогда этоrenderWatcher
Что именно есть?
Наблюдатель Наблюдатель
class Watcher {
value: any;
getter: Function;
constructor (
vm: Component,
expOrFn: string | Function
) {
this.vm = vm
// getter 就是一个render函数
this.getter = expOrFn
this.value = this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
let value;
// 把当前的render `Watcher`推到全局`Dep.target`
pushTarget(this)
// 执行了render函数,可以通过触发property.getter进行对观察(Watcher),也就是当前Watcher的实例this,property的发布者(Dep)就会把观察者(Dep.target)收集起来
value = this.getter();
// 弹出当前的`Watcher`
popTarget()
return value
}
}
Сообщение от Деп
в классеWatcher
Увидев Деп, где определяется издатель?
откуда ты? ? ?
Ну .... Деп определяет реактивные свойстваdefineReactive
Созданное в то время, вот дополнение:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 1.这里创建了一个发布者
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 2.在`getter`时候,进行依赖收集
get: function reactiveGetter () {
if (Dep.target) {
dep.depend()
}
},
// 3.在`setter`时候,通知观察这更新
set: function reactiveSetter (newVal) {
dep.notify()
}
})
}
Суммировать:
-
Каков процесс сбора зависимостей?
Первый сбор происходит при рендеринге, функция рендеринга получает данные слушателем
Observer
перехваченНапример, здесь есть имя атрибута, при рендеринге получается значение имени.
слушатель
Observer
Перехватить операцию получения и активировать атрибут имениgetter
getter
Сбор зависимостей для текущего наблюдателя:dep.depend()
->dep.addSub(watcher)
-
Кто кого собирает?
В сборнике упоминаются 3 персонажа:
- слушатель
Observer
- наблюдатель
Watcher
- диктор
Dep
Вывод по первому пункту: Отдел издателей собирает Watcher Watcher
- слушатель
Подождите.... какова цель этой коллекции?
посмотри вниз....
Уведомление об обновлении данных
<template>
<div>
My name is {{name}}.
</div>
</template>
<script>
export default {
data() {
return {
name: '?'
}
},
created() {
setTimeout(() => {
this.name = 'Yu';
}, 2000);
}
}
</script>
В приведенном выше компоненте есть имя значения атрибута и событие ловушки: обновить значение имени через 2 с.Yu
При первом рендеринге рендеринг строкового шаблона получает атрибут имени, отслеживается слушателем атрибута имени и запускаетgetter
, то отдел издателя собирает текущий наблюдатель-наблюдатель в качестве зависимости.
Через 2 с атрибут имени обновляется, в это время его также отслеживает слушатель атрибута имени, но триггер срабатывает.setter
, согласно вышеизложенному:
// 2.在`setter`时候,通知观察这更新
set: function reactiveSetter (newVal) {
dep.notify()
}
class Dep {
subs: Array<Watcher>;
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Итак, ответ на вышеизложенное здесь:
setter
Что он делает внутри, так это то, что издатель уведомляет наблюдателей, которые были собраны для обновления, а именно:watcher.update()
Войдите отсюдаПроцесс обновления данных
Процесс обновления данных
Ниже приведена функция обновления, основная логика:
- Обновление данных ленивое?
lazy
, если это так, пометьте его как грязные данные и пересчитайте его в зависимости от того, были ли данные грязными при получении данных. Все, что я знаю, это вычисляемые свойстваcomputed
. - Синхронизированы ли обновления данных?
sync
, если да, немедленно выполните обновлениеwatcher.run()
- В противном случае отправьте обновление наАсинхронная очередь обновлений, последующее единое обновление
class Watcher {
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
}
Грубый взгляд на весь процессКарта разума исходного кода VueсерединаПроцесс обновления данных
Подробно здесь описываться не будет.
Здесь есть ключевой момент для асинхронного обновления очереди, вы можете перейти кАсинхронная очередь обновлений
Асинхронная очередь обновлений
/**
* 将观察者推入观察者队列。
* 具有重复ID的watcher将被跳过
* 在刷新队列时推送。
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 如果有重复ID的watcher将被忽略
if (has[id] == null) {
has[id] = true
// 如果没有在冲洗,即还没有开始清空队列
// 将任务watcher添加到队列中
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// 在非等待时候,即可开始启动冲洗队列,呜呜呜~~~
// 只会开启一次哦
//
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
// 不过这里是异步的,就是说:
// 在真正冲洗队列前,还有新的watcher被添加到队列中,
// 直到所有同步代码执行完毕,没有新的watcher了,才是真正开始冲洗
nextTick(flushSchedulerQueue)
}
}
}
очистить очередь
Код в этом блоке относительно прост, то есть он проходит для выполнения метода run каждого наблюдателя, пересчитывает новое значение и выполняет функцию cb наблюдателя. Существует также управление состоянием выполнения наблюдателя, чтобы гарантировать, что каждый наблюдатель будет выполняться только один раз.
function flushSchedulerQueue () {
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
// 其实就执行观察者的run方法
watcher.run()
}
}
class Watcher {
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) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
}
nextTick
Выполните отложенный обратный вызов после завершения следующего цикла обновления DOM, используйте nextTick сразу после изменения данных, чтобы получить обновленный DOM.
nextTick
Есть два способа использования
- прошел дальше
cb
обратный вызов функции, не возвращаетPromise
- Если обратный вызов функции cb не передан, он вернет
Promise
Сначала перейдите к исходному коду:
export function nextTick (cb?: Function, ctx?: Object) {
// promise的resolve控制器
let _resolve
// 根据不同参数,重新包装传进来的`cb`或者`_resolve`
callbacks.push(() => {
// 如果有cb回调,尝试的去调用
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
// 不然的话,就是第二种使用方式,返回一个`Promise`
_resolve(ctx)
}
})
// 这里的代码目的也是保证一轮更新只会被启动一次,不会启动多次
if (!pending) {
pending = true
// 一个根据浏览器差异返回最终的一个异步方案
timerFunc()
}
// 如果传了没有传cb参数,并且客户端有`Promise`这个对象,就返回一个`Promise`
// 也就是第二种使用方式
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
nextTick в основном используетзадача макросаа такжемикрозадачи
Согласно приведенному выше nextTick, обнаружено, что существуетtimerFunc
функция:
Эту функцию в основном пытаются адаптировать в зависимости от среды выполнения:
Обещание, MutationObserver, setImmediate
Если ничего из вышеперечисленного не работает, используйте setTimeout для определения асинхронного метода,
Несколько вызовов nextTick сохранят метод в очереди, а текущая очередь будет очищена с помощью этого асинхронного метода.
Концовка: Ну, весь процесс обновления данных такой, если у вас есть какие-либо вопросы или вы пропустили важные детали, дайте отзыв~