предисловие
Золотая тройка, серебряная четверка почти закончилась, поторопитесь и пересмотрите Vue еще раз.Чтобы выделиться перед интервьюером, часто легче добиться успеха, если вы можете принести осознание и понимание исходного кода при ответе. Вот общие вопросы интервью Vue и соответствующий исходный код и интерпретация, в надежде помочь всем. Длина больше, делится на vue2 и vue3.
vue3 статьиБыл вне
Почерк организую сам, если есть что-то не так или неточно, укажите на это, смиренно приму совет.
Персонально организованный передовой веб-сайт передовых знаний, добро пожаловать на внимание:
Адрес склада:GitHub.com/Чен-младший/…
Адрес веб-сайта:chen-junyi.github.io/article/, внутренний визитjunyi-chen.gitee.io/article/
vue2
1. Принцип отзывчивости Vue
Чтобы ответить на этот вопрос, мы должны сначала понять, что такое отзывчивость. Обычно отзывчивость в vue относится к реакции данных, механизму, с помощью которого можно обнаружить изменения данных и отреагировать на такие изменения. В среде MVVM, такой как Vue, наиболее важным ядром является реализация связи между уровнем данных и уровнем представления посредством приложений, управляемых данными, изменений данных и обновлений представлений. Решение в Vue — захват данных + режим публикации-подписки.
Vue перехватит данные во время инициализации, включая реквизиты, данные, методы, вычисляемые, наблюдатели, и будет выполнять различную обработку в зависимости от типа данных.
Если это объект, используйте Object.defineProperty() для определения перехвата данных:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
return val
},
set(v) {
val = v
notify()
}
})
}
Если это массив, перезапишите 7 методов изменения массива, чтобы реализовать уведомление об изменении:
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
['push','pop','shift','unshift','splice','sort','reverse']
.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
notify()
return result
})
})
Это часть захвата данных, далее поговорим о механизме обновления представления:
- Поскольку функция рендеринга, которую Vue выполняет для компонента, выполняется прокси-сервером Watcher, Watcher перед выполнением назначит самого Watcher глобальной переменной Dep.target и будет ждать, пока свойство Response соберет его.
- Когда компонент выполняет функцию рендеринга, происходит доступ к реактивному свойству, и реактивное свойство точно собирает глобально существующий в данный момент Dep.target как свою собственную зависимость.
- Уведомлять Наблюдателя о повторном вызове при обновлении реактивного свойства.
vm._update(vm._render())Представление компонента обновляется.При обновлении представления разница между новым и старым vnode будет сравниваться через алгоритм diff, а DOM будет обновляться сразу через патч.
2. Что имеет более высокий приоритет между v-if и v-for
Ответ заключается в том, что приоритет парсинга v-for высок, и ответ можно найти в функции genElement в компиляторе/codegen/index.js исходного кода.
function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
let data
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = genData(el, state)
}
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
Встроенные инструкции в vue имеют соответствующие функции синтаксического анализа, а порядок выполнения определяется простым синтаксисом if else-if. В функции genFor в конце будет возвращена самозапускающаяся функция, и снова будет вызван genElement.
Хотя v-for и v-if можно совместить, мы должны избегать такого способа написания.На официальном сайте также четко указано, что это приведет к потерям производительности.
3. Роль ключа
Роль: Используется для определения того, является ли узел виртуального DOM одним и тем же узлом, используемым для оптимизации производительности патча, и патчи - это функция расчета Diff.
Сначала посмотрите на функцию исправления:
На этот раз извлекается только ключевой код, который необходимо проанализировать.
function patch (oldVnode, vnode) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// some code
}
}
return vnode
}
Функция patch получает oldVnode и vnode, которые являются старыми и новыми объектами узла для сравнения.
Во-первых, функция isUndef будет использоваться для определения того, являются ли два входящих vnode пустыми объектами, а затем обрабатывается соответствующим образом. Когда оба являются объектами узла, используйте sameVnode, чтобы определить, являются ли они одним и тем же узлом, а затем определите, будет ли операция добавлять, изменять или удалять.
function sameVnode (a, b) {
return (
a.key === b.key // key值
&&
(
a.tag === b.tag && // 标签名
a.isComment === b.isComment && // 是否为注释节点
isDef(a.data) === isDef(b.data) && // 是否都定义了data,data包含一些具体信息,例如onclick , style
sameInputType(a, b) // 当标签是<input>的时候,type必须相同
)
)
}
sameVnode решает, требуется ли сравнение, определяя, являются ли ключ, имя метки, является ли это комментарием и равны ли данные.
Если стоит сравнивать, выполнить patchVnode, если сравнивать не стоит, заменить oldVnode на Vnode, а потом рендерить настоящий dom.
patchVnode сравнит oldVnode и vnode, а затем обновит DOM. Это будет объяснено в алгоритме сравнения.
v-for обычно генерируют одну и ту же метку, поэтому ключ является уникальным идентификатором, патч будет определять, является ли тот же узел, если вы не установите ключ, его значение не определено, два никогда не могут подумать, что это один и тот же узел, вы будете делать это pathVnode pdateChildren операция обновления, которая вызвала большое количество операций обновления dom, поэтому необходимо установить уникальный ключ.
4. Принцип двусторонней привязки
Двусторонняя привязка в vue — это директивная v-модель, которая может привязывать динамическое значение к представлению, и изменения в представлении могут изменить значение. v-model — это синтаксический сахар, эквивалентный :value и @input по умолчанию.
Обычно v-model можно использовать непосредственно в элементах формы, когда Vue анализирует эти элементы формы и автоматически выбирает правильный метод для обновления элементов в соответствии с типом элемента управления.
v-model внутренне использует разные свойства и выдает разные события для разных элементов ввода:
- элементы text и textarea используют свойство value и событие ввода;
- флажок и радио используют проверенное свойство и событие изменения;
- Поле выбора имеет значение как реквизит и изменение как событие.
Если это настраиваемый компонент, для его использования вам необходимо привязать значение реквизита в компоненте и использовать $emit('input') при обновлении данных. Вы также можете определить модальное свойство в компоненте, чтобы настроить привязку имя свойства и имя события. .
model: {
prop: 'checked',
event: 'change'
}
5. Принцип nextTick
Сначала посмотрите официальную документацию:
Vue выполняется асинхронно при обновлении DOM. Как только он узнает об изменении данных, Vue откроет очередь и буферизует все изменения данных, которые происходят в том же цикле событий. Если один и тот же наблюдатель запускается несколько раз, он будет помещен в очередь только один раз.
nextTick должен поместить функцию обратного вызова в очередь, чтобы убедиться, что обновленный DOM получен за наблюдателем, который асинхронно обновляет DOM.
В сочетании с исходным кодом src/core/util/next-tick и последующим анализом.
Во-первых, определить метод очереди задач выполнения.
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
Функции обратного вызова выполняются в том порядке, в котором они были помещены в очередь обратных вызовов.
Затем определите функцию timerFunc, чтобы определить, какой асинхронный метод вызывать в соответствии с тем, какие методы поддерживаются текущей средой.
Порядок суждения таков:Promise > MutationObserver > setImmediate > setTimeout
Наконец, определите метод nextTick:
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
По сути, nextTick — это метод для помещения функции обратного вызова в очередь задач.
Я так понимаю, что это почти одно и то же.Если копнуть глубже, то можно сказать, что данные в vue меняются, срабатывает вотчер, и вотчер входит в очередь.Можете почитать другую мою статью.Полный анализ nextTick в vue.
6. Почему данные — это функция
Если данные в компоненте напрямую записывают объект, то если компонент объявлен в шаблоне несколько раз, данные в компоненте будут указывать на одну и ту же ссылку.
Изменение данных в настоящее время приведет к изменению данных в других компонентах. Используйте функцию для Redeclare объекта каждый раз, чтобы данные каждого компонента имели свою ссылку, и не будет взаимного загрязнения.
7. Компонентный метод связи
- реквизит и
$on,$emit
Подходит для связи между родительскими и дочерними компонентами, реагирующие данные передаются через реквизиты, а родительские компоненты проходят$onПрослушивание событий, передача дочерних компонентов$emitОтправить события.
on и emit передаются при инициализации экземпляра компонентаinitEventsИнициализируйте событие, назначьте пустой объект события экземпляру компонента vm._events и реализуйте публикацию событий и подписку через этот объект. Ниже приведены несколько ключевых функций регистрации событий:
// 组件初始化event对象,收集要监听的事件和对应的回调函数
function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
...
// 注册组件监听的事件
function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
-
ref,$parent,$children,а также$root
- ref: Объявление общего элемента DOM — это ссылка на элемент DOM, а компонент — указатель на экземпляр компонента.
- $parent: доступ к экземпляру родительского компонента компонента
- $ Children: Доступ ко всей коллекции подкомпонентов (массивов)
- $root: указывает на корневой экземпляр
- Event Bus
обычно создают空的Vue实例作为事件总线(事件中心), чтобы обеспечить запуск событий и мониторинг любого компонента в этом экземпляре. Принцип представляет собой модель публикации-подписки, с$on``$emitТочно так же в случае инстанцирования компонента пустой объект события инициализируется через initEvents, а затем вручную через инстанцируемую шину (vue instance)$on,$emitДобавьте события прослушивания и запуска, код находится вsrc/core/instance/events:
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
// 传入的事件如果是数组,就循环监听每个事件
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 如果已经有这个事件,就push新的回调函数进去,没有则先赋值空数组再push
(vm._events[event] || (vm._events[event] = [])).push(fn)
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
...
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
...
let cbs = vm._events[event]
// 循环调用要触发的事件的回调函数数组
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
- listeners
-
$attrs: включает родительскую область没被props声明Привязка данных, компонент может пройтиv-bind="$attrs"Продолжайте проходить подсагблировать -
$listernes: содержит родительский объемv-on(без декоратора .native) слушайте события, доступ к которым можно получить черезv-on="$listeners"Пропустить внутренние компоненты
- обеспечивать, вводить
Родительский компонент внедряет зависимость через Provide, а все его компоненты-потомки могут быть получены через inject. Следует отметить, что на официальном сайте есть такой пункт:
Совет: привязки обеспечить и внедрить не являются реактивными. Это сделано намеренно. Однако, если вы передаете прослушиваемый объект, свойства объекта по-прежнему доступны.
Таким образом, Vue не будет выполнять реактивную обработку переменных в файле Provide. Чтобы переменные, принимаемые инжектом, были реактивными, переменные, предоставленные провайдером, сами должны быть реактивными. На самом деле, во многих продвинутых компонентах можно увидеть, что компоненты передают это компонентам-потомкам через предоставление, включая element-ui, ant-design-vue и т. д.
- Государственное управление для связи Vuex
vuex разработан специально для управления состоянием vue. Каждый экземпляр имеет общий экземпляр компонента хранилища и реагирует на store.state, единственный способ изменить состояние - это мутация в хранилище фиксации этого примера, легко отслеживать изменения в каждом состоянии, следующий принцип реализации vuex есть обсуждение о принципах.
8. В чем разница между вычисляемым, наблюдательным и методом
вычисляется: есть кеш, есть соответствующий наблюдатель, у наблюдателя есть атрибут lazy true, что означает, что он будет вычисляться только после считывания его значения в шаблоне, а наблюдатель присвоит dirty значение true при инициализации , и у наблюдателя есть только Когда dirty равно true, он будет переоценен. После переоценки грязный будет установлен в false, а false будет напрямую возвращать значение наблюдателя. Только в следующий раз отзывчивая зависимость обновляется наблюдатель, грязный наблюдатель будет сброшен.Если это ложь, он будет переоценен в это время, таким образом реализуя вычисляемый кеш.
watch: функция выполняется каждый раз, когда обновляется объект наблюдателя. watch больше подходит для асинхронных операций при изменении данных. Если вам нужно что-то делать при изменении некоторых данных, используйте watch.
метод: метод используется в шаблоне, и функция будет повторно выполняться каждый раз при обновлении представления, что потребляет много производительности.
9. Жизненный цикл
Описание жизненного цикла на официальном сайте:
Каждый экземпляр Vue при создании проходит ряд процессов инициализации — например, ему необходимо настроить прослушиватели данных, скомпилировать шаблоны, смонтировать экземпляр в DOM и обновить DOM при изменении данных и т. д. В то же время во время этого процесса также запускаются некоторые функции, называемые хуками жизненного цикла, что дает пользователям возможность добавлять свой собственный код на разных этапах.
Жизненный цикл — это ловушка для каждого экземпляра Vue, позволяющая выполнить ряд действий по инициализации, запуску и уничтожению.
В основном вы можете сказать 8 этапов до/после создания, до/после загрузки, до/после обновления, до/после уничтожения.
- До/после создания: на этапе beforeCreate элемент монтирования el экземпляра vue еще не создан.
- До/после загрузки: на этапе beforeMount $el и данные экземпляра vue инициализируются, но до монтирования они все еще являются виртуальными узлами dom, а data.message не был заменен. На этапе монтирования экземпляр vue монтируется, и data.message успешно обрабатывается.
- До/после обновления: при изменении данных запускаются методы beforeUpdate и updated.
- До/после уничтожения: после выполнения метода destroy изменения в данных больше не будут запускать периодическую функцию, указывая на то, что экземпляр vue был освобожден от отслеживания событий и привязки к dom в это время, но структура dom все еще существует.
В сочетании с исходным кодом для понимания хук жизненного цикла в исходном коде вызывается с помощью функции CallHook. См. функцию Callhook:
function callHook (vm: Component, hook: string) {
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
Параметры Vm и получить экземпляр компонента хука, возьмем в качестве примера компоненты входящего значения свойства $ options хука, которое вызывается в цикле функции обратного вызова хука. Временно pushTarget имеет нулевое значение в жизни перед вызовом функции обратного вызова хука, то есть Dep.target устанавливается пустым, чтобы отключить зависимость, собранную в реализации хука жизни.
vm.$emit('hook:' + hook) используется для отслеживания события обратного вызова компонента для родительского компонента.
Затем посмотрите на время конкретного звонка каждого крючка жизни.
1. перед созданием создано:
Vue.prototype._init = function (options?: Object) {
...
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
вызывается перед выполнением beforeCreateinitLifecycle、initEvents、initRenderфункция, поэтому beforeCreate — это жизненный цикл после жизненного цикла инициализации, событий и функций рендеринга.
Перед созданием выполнения называются initinjections, initate, initprovide, на этот раз создали инициализированные данные, реквизиты, наблюдателя, предоставлять, вводить и т. Д., Так как время мы можем получить доступ к данным, реквизиту и другим атрибутам.
2. beforeMount, установленный
В приведенном выше фрагменте кода вы можете видеть, что DOM будет смонтирован после создания, а выполняемая функция — vm.options.el), затем проанализируйте метод $mount.
vm.Унаследовано от метода прототипа монтирования. Этот метод находится вsrc/platforms/web/entry-runtime-with-compiler.jsСледующий оператор в основном используется для анализа шаблона, и сначала нужно определить, есть ли атрибут функции рендеринга, и нет ли разбора шаблона tampare.В конце концов, функция рендеринга используется для рендеринга.
CallHook (VM, «BeForemount») вызывается после анализа функции рендеринга, затем выполнить VM._RENDER (), затем MALHOOK (VM, «Установленный»), который помечен в недавно созданный VM. $ EL замена и установленные на экземпляре
3. перед обновлением, обновлено
Эти две функции-ловушки — это функции, которые вызываются при обновлении данных. существуетsrc/core/instance/lifecycle.jsНайдите код перед вызовом Update:
...
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...
_Ismounted - Ture (DOM был установлен) вызывает метод CallHook (VM, 'roupdate'), а затем повторно рендеринг виртуального DOM. Тогда в функции SppleSchedUlerQueue () под /src/core/observer/scheduler.js sppleschedulerqueue будет обновлять очередь наблюдателя и выполнить, после выполнения всех методов запуска наблюдателя (метод запуска - это Watcher для DO DOM DAFT и UPDATE DOM) Позвоните CallHook (VM, «Обновлено»), код выглядит следующим образом:
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
...
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
watcher.run()
}
...
callUpdatedHooks(updatedQueue)
...
}
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
4. beforeDestroy, уничтожено
Эти два хука — хуки для уничтожения экземпляра vue, которые определены в Vue.prototype.$destroy:
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
Исполнение CALLHOOK (VM, «BEFOREDESTROY») до разрушения, то разрушение времени делать несколько вещей:
- Если есть родительский элемент, удалите экземпляр компонента из $children родительского элемента.
- Удалить наблюдателей и удалите себя в зависимых подписчиках.
- удалить ссылку на данные
5. активирован, деактивирован
остальныеactivated、deactivated、errorCapturedТри функции крючка.
Две активированные и деактивированные функции ловушек являются обратными вызовами после активации и деактивации компонента поддержки активности соответственно.
errorCaptured фиксирует, что он будет вызываться при сбое компонента-потомка.В исходном коде часто можно увидеть, что catch в try catch вызовет функцию handleError, а handleError выдаст исключение всем родительским компонентам компонента.
function handleError (err: Error, vm: any, info: string) {
pushTarget()
try {
if (vm) {
let cur = vm
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e) {
globalHandleError(e, cur, 'errorCaptured hook')
}
}
}
}
}
globalHandleError(err, vm, info)
} finally {
popTarget()
}
}
Проанализировав исходный код, посмотрите на иконку официального сайта, так будет понятнее:
10. принцип поддержания активности
keep-alive — это встроенный компонент Vue.js. У него есть возможность хранить экземпляры неактивных компонентов в памяти, а не уничтожать их напрямую. Это абстрактный компонент, который не отображается в реальном DOM и не появляется в цепочке родительских компонентов.
Свойства включения и исключения позволяют условно кэшировать компоненты, а свойство max определяет максимальное количество кэшируемых экземпляров компонентов.
keep-alive — это компонент, который имеет тот же жизненный цикл и функцию рендеринга, что и другие компоненты.Анализ пакета keep-alive — это анализ компонента.
исходный код сноваsrc/core/components/keep-alive, created объявляет объект компонента кэшируемым и сохраненные ключи компонента.При уничтожении keep-alive будет использоваться pruneCacheEntry для уничтожения всех кэшированных экземпляров компонента, то есть будет вызываться метод destroy экземпляра компонента. После завершения монтирования отслеживайте включение и исключение и динамически уничтожайте компоненты, которые не удовлетворяют требованиям включения, и экземпляры компонентов, удовлетворяющие условиям исключения:
created () {
this.cache = Object.create(null) // 存储需要缓存的组件
this.keys = [] // 存储每个需要缓存的组件的key,即对应this.cache对象中的键值
},
// 销毁keep-alive组件的时候,对缓存中的每个组件执行销毁
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
Далее идет функция рендеринга:
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
// 如果vnode存在就取vnode的选项
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
//获取第一个有效组件的name
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode// 说明不用缓存,直接返回这个组件进行渲染
}
// 匹配到了,开始缓存操作
const { cache, keys } = this // keep-alive组件的缓存组件和缓存组件对应的key
// 获取第一个有效组件的key
const key: ?string = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
// 这个组件的实例用缓存中的组件实例替换
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
// 更新当前key在keys中的位置
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
// 如果缓存中的组件个数超过传入的max,销毁缓存中的LRU组件
// LRU: least recently used 最近最少用,缓存淘汰策略
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
// 若第一个有效的组件存在,但其componentOptions不存在,就返回这个组件进行渲染
// 或若也不存在有效的第一个组件,但keep-alive组件的默认插槽存在,就返回默认插槽的第一个组件进行渲染
return vnode || (slot && slot[0])
}
Код подробно прокомментирован, и вот что делает рендер.
Получите компонент слота через this.$slots.default, то есть компонент, обернутый keep-alive, getFirstComponentChild, чтобы получить первый дочерний компонент, и получите имя компонента (если есть имя компонента, используйте имя компонента напрямую, иначе будет использоваться тег). Далее имя будет сопоставляться с атрибутами включения и исключения.Если совпадение будет неудачным (что указывает на то, что кэширование не требуется), никакая операция не будет выполняться, а vnode будет возвращен напрямую.(vnode节点描述对象,vue通过vnode创建真实的DOM).
При достижении совпадения запускается кеш, и ищется в this.cache по ключу, если он есть, то значит он уже был закэширован ранее, а componentInstance (экземпляр компонента) закешированного vnode находится напрямую перезаписывается на текущем vnode. В противном случае сохраните vnode в кеше. А через remove(keys, key) удалить текущий ключ из keys и повторно использовать keys.push(key), тем самым изменив положение текущего ключа в keys. Это необходимо для достижения функции max и следования стратегии ликвидации кеша.
Если совпадения нет, значит он не был закеширован.В это время необходимо закешировать и определить, превышает ли текущее количество кешей число, указанное max.Если превышает, последний компонент в ключах будет быть уничтожены и сняты с ключей.Это ЛРУ(Least Recently Used :最近最少使用 ) алгоритм устранения кеша.
Наконец, верните vnode или первый компонент слота по умолчанию для рендеринга DOM.
12. Виртуальный дом и алгоритм сравнения
Виртуальный DOM — это описание DOM, использующее свойства объекта для описания узлов, которые по сути являются объектами JavaScript. Он имеет несколько значений:
- Имеет преимущество кроссплатформенности
Поскольку виртуальный DOM основан на объектах JavaScript и не зависит от реальной среды платформы, он имеет межплатформенные возможности, такие как браузеры, апплеты, Node, нативные приложения, рендеринг на стороне сервера и многое другое.
- Улучшить производительность рендеринга
Частые изменения в DOM вызовут перекомпоновку или возврат браузера. Перенося большое количество операций DOM в Javascript и используя алгоритм исправления для вычисления узлов, которые действительно нуждаются в обновлении, количество реальных операций DOM можно уменьшить , тем самым повышая производительность.
- Код может быть больше обслуживаемости
Благодаря абстрагирующей способности виртуального DOM мы можем писать пользовательский интерфейс декларативно, что значительно повышает эффективность нашей работы.
В vue шаблон в конечном итоге будет преобразован в функцию рендеринга, а функция рендеринга, наконец, выполняется createElement, который генерирует vnode, который является классом, используемым для представления виртуального DOM в vue, посмотрите на vnode:
class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
Посмотрите, где несколько ключевых атрибутов:
-
tag: имя тега текущего узла
-
data: представляет класс, атрибут, стиль и связанные события на узле
-
Children: дочерние узлы текущего узла, который представляет собой массив
-
text: текст текущего узла
-
elm: реальный узел dom, соответствующий текущему виртуальному узлу.
-
ключ: ключевой атрибут узла, который используется в качестве флага узла для оптимизации.
-
componentOptions: опции опций компонента
-
componentInstance: экземпляр компонента, соответствующий текущему узлу.
-
Родитель: родительский узел текущего узла.
-
isStatic: является ли это статическим узлом
Дочерние и родительские узлы относятся к дочерним узлам и родительским узлам текущего vnode, так что каждый vnode формирует дерево DOM.
Алгоритм сравнения происходит в视图更新когда данные обновляются,diff算法会将新旧虚拟DOM作对比,将变化的地方转换为DOM.
Когда определенные данные будут изменены, соответствующий наблюдатель уведомит об обновлении, и выполнение функции рендеринга сгенерирует новый vnode, а затем vnode будет сравниваться и обновляться со старым vnode.Этот процесс инициируется виртуальным Алгоритм dom diff в vue.
Посмотрите на метод _Update обновления компонентов:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
...
VM.$EL = VM._PATCH(), это окончательный визуализированный DOM-элемент, который является функцией алгоритма Diff во Vue, который упоминается в пошаговой главе Key. После того, как Patch сравнивает недавно созданный виртуальный узел DOM, наконец возвращает реальный узел DOM.
patch
Посмотрите код патча (часть):
function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
/*vnode不存在则直接调用销毁钩子*/
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue, parentElm, refElm)
} else {
/*标记旧的VNode是否有nodeType*/
/*Github:https://github.com/answershuto*/
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
/*是同一个节点的时候直接修改现有的节点*/
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
...
return vnode.elm
Во-первых, определить, есть ли новый vnode, если представителя нет, старый vnode подлежит уничтожению и вызывается хук для уничтожения компонента.
Затем определить, есть ли старый vnode, если представителя нет, то это новое дополнение, то есть новый корневой узел.
Затем оцените, является ли старый vnode реальным элементом, а не компонентом.Если это компонент, и используйте someVnode, чтобы определить, являются ли старый и новый узлы одним и тем же узлом (sameVnode анализируется в главе о роли ключа), это patchVnode, а затем реальная разница между старыми и новыми узлами.只有相同的节点才会进行diff算法!!!
patchVnode
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
// 两个vnode相同,说明不需要diff,直接返回
if (oldVnode === vnode) {
return
}
// 如果传入了ownerArray和index,可以进行重用vnode,updateChildren里用来替换位置
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
const elm = vnode.elm = oldVnode.elm
// 如果oldVnode的isAsyncPlaceholder属性为true时,跳过检查异步组件,return
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
/*
如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),
并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),
那么只需要替换elm以及componentInstance即可。
*/
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
/*如果这个VNode节点没有text文本时*/
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
// 两个vnode都定义了子节点,并且不相同,就对子节点进行diff
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
// 如果只有新的vnode定义了子节点,则进行添加子节点的操作
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// 如果只有旧的vnode定义了子节点,则进行删除子节点的操作
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
Из кода видно, что patchVnode разбивается на различные ситуации, и анализируется процесс diff подузлов.(oldCh 为 oldVnode的子节点,ch 为 Vnode的子节点)
- oldCh, ch определены для вызова updateChildren, а затем diff
- Если oldCh не существует, а ch существует, сначала очистите текстовый узел oldVnode и вызовите метод addVnodes, чтобы добавить ch к узлу real dom elm.
- Если oldCh существует, а ch не существует, удалите дочерний узел oldCh под реальным узлом elm.
- Если у oldVnode есть текстовые узлы, а у vnode нет, то очистить текстовый узел
updateChildrenЭто функция подузла diff, а также самая важная ссылка.
updateChildren
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// 声明oldCh和newCh的头尾索引和头尾的vnode,
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
// 判断两边的头是不是相同节点
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
// 判断尾部是不是相同节点
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
// 判断旧节点头部是不是与新节点的尾部相同,相同则把头部往右移
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
// 判断旧节点尾部是不是与新节点的头部相同,相同则把头部往左移
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
/*
生成一个key与旧VNode的key对应的哈希表
*/
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
// oldCh或者newCh遍历完,说明剩下的节点不是新增就是删除
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
Сначала в качестве индексов обхода используются startIndex и endIndex.При обходе сначала будет судить, совпадают ли головной и хвостовой узлы.Если одинаковых узлов не найдено, то обход и поиск в общем порядке, после поиска обработка остальные узлы по ситуации; идентичные узлы можно найти очень точно.
При обходе oldCh или newCh (условие обхода startIndex >= endIndex oldCh или newCh) это означает, что оставшиеся узлы добавляются или удаляются, а дифф между oldCh и newCh в это время останавливается.
13. Принцип Vuex
Что такое vuex смотрите под словами чиновника:
Vuex — это шаблон управления состоянием, разработанный для приложений Vue.js. Он использует централизованное хранилище для управления состоянием всех компонентов приложения и использует соответствующие правила для обеспечения предсказуемого изменения состояния.
Из этого отрывка можно сделать несколько выводов:Vuex是为vue.js服务的, и подобно тому, как redux и react отделены друг от друга, тогда vuex — это режим управления состоянием, все состояния изменяются предсказуемым образом.
Дизайнерское мышление:
Идея дизайна Vuex опирается на Flux и Redux для хранения данных в глобальном хранилище, а затем монтирует хранилище к каждому компоненту экземпляра vue и использует детальный механизм ответа на данные Vue.js для выполнения эффективных обновлений состояния.
Принцип может быть проанализирован с момента использования.
Vue.use(Vuex); // 1. vue的插件机制,安装vuex
let store = new Vuex.Store({ // 2.实例化store,调用install方法
state,
getters,
modules,
mutations,
actions,
plugins
});
new Vue({ // 3.注入store, 挂载vue实例
store,
render: h=>h(app)
}).$mount('#app');
Vue.use — это механизм подключаемого модуля в vue.Метод установки подключаемого модуля будет вызываться внутри, а метод установки vuex:
export function install (_Vue) {
if (Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
/*保存Vue,同时用于检测是否重复安装*/
Vue = _Vue
/*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
applyMixin(Vue)
}
Vuex — это глобальное управление состоянием.В глобальном масштабе может быть только один экземпляр хранилища, поэтому при установке он будет судить, был ли он установлен.Это режим singleton, который гарантирует наличие только одного экземпляра класса. При первой установке будет применяться applyMixin, а applyMixin/src/mixinМетод импорта:
function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
Сначала оцените версию vue и проанализируйте здесь логику vue2. Используя механизм микширования Vue.mixin, метод vuexInit вызывается в beforeCreate экземпляра компонента. Сначала оценивается, есть ли у опций хранилище, и ни один из представителей не является корневым узлом. В это время хранилище должно быть Если нет, назначается $store родительского компонента, так что реализован глобально общий и уникальный экземпляр хранилища.
Исходный код реализации магазина находится вsrc/store.js, ядром которого является адаптивная реализация, вызываемая через resetStoreVM(this, state), посмотрите на этот метод:
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
RESESTOREVM сначала пройдет к ворчаркам и использует метод Object.DefineProperty для определения метода получения каждого добыча магазина. GetTers, чтобы получить доступ к этому.
Состояние заключается в реализации «отзывчивости» данных через новый объект Vue, использовании атрибута данных Vue для реализации синхронного обновления данных и представления, а вычисление реализует вычисляемые свойства геттеров. Окончательный доступ к store.state — это доступ к store._vm.state.
заключительные замечания
Если вы ошибаетесь, можете указать на это, спасибо за совет~
Добро пожаловать, чтобы подписаться и поставить лайк, всем спасибо 😁