написать впереди
Поскольку меня очень интересует Vue.js, а технологический стек, над которым я обычно работаю, тоже Vue.js, я потратил некоторое время на изучение и изучение исходного кода Vue.js за последние несколько месяцев и сделал резюме и вывод.
Оригинальный адрес статьи:GitHub.com/ответы на….
В процессе обучения в Vue были добавлены китайские аннотации.GitHub.com/ответы на…, я надеюсь, что это может быть полезно другим друзьям, которые хотят изучить исходный код Vue.
Могут быть некоторые отклонения в понимании.Вы можете поднимать вопросы и указывать, учиться вместе и добиваться прогресса вместе.
VNode
В эпоху резки и записи нам нужно напрямую манипулировать DOM в каждом методе события, чтобы достичь цели изменения представления. Но когда приложение большое, его становится сложно поддерживать.
Итак, можем ли мы абстрагировать реальное дерево DOM в абстрактное дерево, состоящее из объектов JavaScript, а затем преобразовать абстрактное дерево в настоящий DOM и перерисовать его на странице после изменения данных абстрактного дерева? Так появился виртуальный DOM, который представляет собой слой абстракции реального DOM и использует атрибуты для описания различных характеристик реального DOM. Когда он изменяется, вид изменяется.
Но такие манипуляции JavaScript с DOM для перерисовки всего слоя просмотра потребляют производительность, Можем ли мы каждый раз обновлять только его модификации? Поэтому Vue.js абстрагирует DOM в виртуальное дерево DOM с объектами JavaScript в качестве узлов, имитирует реальный DOM с узлами VNode и может создавать узлы, удалять узлы и изменять узлы в этом абстрактном дереве. нужно только управлять объектом JavaScript, что значительно повышает производительность. После модификации некоторые минимальные единицы, которые необходимо изменить, получаются с помощью алгоритма diff, а затем обновляются представления этих небольших единиц. Это уменьшает количество ненужных манипуляций с DOM и значительно повышает производительность.
Vue использует такой абстрактный узел VNode, который является абстракцией реального Dom, не полагаясь на определенную платформу.Это может быть платформа браузера или weex, и даже платформа узла может абстрагировать такое дерево.Дерево Dom выполняет такие операции, как создание, удаление и модификация, что также обеспечивает возможность изоморфизма внешнего и внутреннего интерфейса.
Детали конкретного VNode можно увидетьУзел VNode.
Изменить представление
Как мы все знаем, Vue изменяет представление через привязку данных.Когда определенные данные изменяются, метод set заставит Dep в вызове закрытия уведомить об уведомлении всех подписчиков Watcher, а Watcher выполнит vm._update(vm._render(), увлажнение).
Вот посмотрите на метод _update
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
/*如果已经该组件已经挂载过了则代表进入这个步骤是个更新的过程,触发beforeUpdate钩子*/
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
/*基于后端渲染Vue.prototype.__patch__被用来作为一个入口*/
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
/*更新新的实例对象的__vue__*/
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
Первый параметр метода _update — это объект VNode, который внутренне сравнивает объект VNode со старым объектом VNode.patch.
чтоpatchШерстяная ткань?
patch
Патч сравнивает новый и старый узлы VNode, а затем изменяет представление в наименьшей единице в соответствии с результатом сравнения двух вместо перерисовки всего представления в соответствии с новым VNode. В основе патча лежит алгоритм сравнения, который может эффективно сравнивать изменения витурл-дома и получать изменения для изменения представления.
Итак, как работает патч?
Прежде всего, давайте поговорим об основном алгоритме сравнения патчей.Алгоритм сравнения сравнивает узлы дерева одного и того же слоя вместо поиска и обхода дерева слой за слоем, поэтому временная сложность составляет всего O (n), что является достаточно эффективный алгоритм.
Два изображения представляют процесс исправления старого VNode и нового VNode.Они просто сравнивают и изменяют между VNode на одном уровне (одинаковые цветные квадраты на втором рисунке представляют узлы VNode, которые сравниваются друг с другом), а затем изменить измененный вид, так что это очень эффективно.
Давайте посмотрим на код патча.
/*createPatchFunction的返回值,一个patch函数*/
return 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
/*oldVnode未定义的时候,其实也就是root节点,创建一个新的节点*/
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)
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
/*当旧的VNode是服务端渲染的元素,hydrating记为true*/
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
/*需要合并到真实DOM上*/
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
/*调用insert钩子*/
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
/*如果不是服务端渲染或者合并到真实DOM失败,则创建一个空的VNode节点替换它*/
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
/*取代现有元素*/
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
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)
)
if (isDef(vnode.parent)) {
// component root element replaced.
// update parent placeholder node element, recursively
/*组件根节点被替换,遍历更新父节点element*/
let ancestor = vnode.parent
while (ancestor) {
ancestor.elm = vnode.elm
ancestor = ancestor.parent
}
if (isPatchable(vnode)) {
/*调用create回调*/
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode.parent)
}
}
}
if (isDef(parentElm)) {
/*移除老节点*/
removeVnodes(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
/*Github:https://github.com/answershuto*/
/*调用destroy钩子*/
invokeDestroyHook(oldVnode)
}
}
}
/*调用insert钩子*/
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
Нетрудно найти из кода, который PatchVnode будет выполнен только при Oldvnode и Vnode в SymVnode, то есть процесс patchVnode будет выполнен только тогда, когда старые и новые узлы Vnode определяются тем же узлом, в противном случае будет создан новый дом, а старый дом будет удален.,
Что за узел тот же Vnode?
sameVnode
Давайте взглянем на реализацию sameVnode.
/*
判断两个VNode节点是否是同一个节点,需要满足以下条件
key相同
tag(当前节点的标签名)相同
isComment(是否为注释节点)相同
是否data(当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息)都有定义
当标签是<input>的时候,type必须相同
*/
function sameVnode (a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
}
// Some browsers do not support dynamically changing type for <input>
// so they need to be treated as different nodes
/*
判断当标签是<input>的时候,type是否相同
某些浏览器不支持动态修改<input>类型,所以他们被视为不同类型
*/
function sameInputType (a, b) {
if (a.tag !== 'input') return true
let i
const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
return typeA === typeB
}
Когда тег, ключ и isComment двух VNodes одинаковы, а данные определены или не определены одновременно, и если тег является входным, тип должен быть одинаковым. В это время два VNode рассматриваются как один и тот же Vnode, и операция patchVnode может выполняться напрямую.
patchVnode
Давайте сначала посмотрим на код patchVnode.
/*patch VNode节点*/
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
/*两个VNode节点相同则直接返回*/
if (oldVnode === vnode) {
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
/*
如果新旧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.elm = oldVnode.elm
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
/*i = data.hook.prepatch,如果存在的话,见"./create-component componentVNodeHooks"。*/
i(oldVnode, vnode)
}
const elm = vnode.elm = oldVnode.elm
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
/*调用update回调以及update钩子*/
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)) {
/*新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren*/
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
/*如果老节点没有子节点而新节点存在子节点,先清空elm的文本内容,然后为当前节点加入子节点*/
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
/*当新节点没有子节点而老节点有子节点的时候,则移除所有ele的子节点*/
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
/*当新老节点都无子节点的时候,只是文本的替换,因为这个逻辑中新节点text不存在,所以直接去除ele的文本*/
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
/*当新老节点text不一样时,直接替换这段文本*/
nodeOps.setTextContent(elm, vnode.text)
}
/*调用postpatch钩子*/
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
Правила для patchVnode следующие:
1. Если старый и новый виртуальные узлы являются статическими, а их ключи одинаковыми (представляющими один и тот же узел), а новый виртуальный узел клонируется или помечается один раз (отмеченный атрибут v-once, визуализируется только один раз), то необходимо только заменить elm и componentInstance могут быть.
2. Если и у старого, и у нового узла есть дочерние дочерние узлы, операция diff выполняется над дочерними узлами и вызывается updateChildren, который также является ядром diff.
3. Если у старого узла нет дочерних узлов, а у нового узла есть дочерние узлы, сначала очистите текстовое содержимое DOM старого узла, а затем добавьте дочерние узлы к текущему узлу DOM.
4. Когда у нового узла нет дочерних узлов, а у старого узла есть дочерние узлы, удалите все дочерние узлы узла DOM.
5. Когда старый и новый узлы не имеют дочерних узлов, это просто замена текста.
updateChildren
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
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, elmToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly
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)) {
/*前四种情况其实是指定key的时候,判定为同一个VNode,则直接patchVnode即可,分别比较oldCh以及newCh的两头节点2*2=4种情况*/
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
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)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
/*
生成一个key与旧VNode的key对应的哈希表(只有第一次进来undefined的时候会生成,也为后面检测重复的key值做铺垫)
比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}] beginIdx = 0 endIdx = 2
结果生成{key0: 0, key1: 1, key2: 2}
*/
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
/*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld(即第几个节点,下标)*/
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
if (isUndef(idxInOld)) { // New element
/*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
} else {
/*获取同key的老节点*/
elmToMove = oldCh[idxInOld]
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !elmToMove) {
/*如果elmToMove不存在说明之前已经有新节点放入过这个key的DOM中,提示可能存在重复的key,确保v-for的时候item有唯一的key值*/
warn(
'It seems there are duplicate keys that is causing an update error. ' +
'Make sure each v-for item has a unique key.'
)
}
if (sameVnode(elmToMove, newStartVnode)) {
/*Github:https://github.com/answershuto*/
/*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
/*因为已经patchVnode进去了,所以将这个老节点赋值undefined,之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/
oldCh[idxInOld] = undefined
/*当有标识位canMove实可以直接插入oldStartVnode对应的真实DOM节点前面*/
canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
} else {
// same key but different element. treat as new element
/*当新的VNode与找到的同样key的VNode不是sameVNode的时候(比如说tag不一样或者是有不一样type的input标签),创建一个新的节点*/
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
}
}
}
if (oldStartIdx > oldEndIdx) {
/*全部比较完成以后,发现oldStartIdx > oldEndIdx的话,说明老节点已经遍历完了,新节点比老节点多,所以这时候多出来的新节点需要一个一个创建出来加入到真实DOM中*/
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
/*如果全部比较完成以后发现newStartIdx > newEndIdx,则说明新节点已经遍历完了,老节点多余新节点,这个时候需要将多余的老节点从真实DOM中移除*/
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
Глядя непосредственно на исходный код, может быть трудно отфильтровать взаимосвязь, давайте посмотрим на нее через рисунок.
Во-первых, слева и справа от нового и старого узлов VNode есть переменный маркер, и эти переменные будут перемещаться ближе к середине в процессе обхода. Завершите цикл, когда oldStartIdx
Соответствующая связь между индексом и узлом VNode:
oldStartIdx => oldStartVnode
oldEndIdx => oldEndVnode
newStartIdx => newStartVnode
newEndIdx => newEndVnode
При обходе, если ключ существует и тот же Vnode удовлетворяется, узел DOM будет использоваться повторно, в противном случае будет создан новый узел DOM.
Во-первых, всего существует 2*2=4 метода сравнения для парного сравнения oldStartVnode, oldEndVnode и newStartVnode, newEndVnode.
Когда начало или конец старого и нового узлов VNode удовлетворяют тому же самому Vnode, то есть тому же самому Vnode(oldStartVnode, newStartVnode) или sameVnode(oldEndVnode, newEndVnode), вы можете напрямую исправить узел VNode.
Если oldStartVnode и newEndVnode удовлетворяют sameVnode, то есть sameVnode(oldStartVnode, newEndVnode).
В настоящее время это означает, что oldStartVnode работает после oldEndVnode.Когда patchVnode выполняется, реальный узел DOM должен быть перемещен за oldEndVnode.
Если oldEndVnode и newStartVnode удовлетворяют sameVnode, то есть sameVnode(oldEndVnode, newStartVnode).
Это показывает, что oldEndVnode располагался впереди oldStartVnode, а реальный узел DOM перемещался впереди oldStartVnode во время выполнения patchVnode.
Если ни одно из вышеперечисленных условий не выполняется, через createKeyToOldIdx будет получен oldKeyToIdx, в котором ключ — это старый VNode, а значение — хэш-таблица, соответствующая индексной последовательности. Из этой хэш-таблицы вы можете узнать, существует ли старый узел VNode с тем же ключом, что и newStartVnode.Если тот же самый Vnode одновременно удовлетворяется, patchVnode переместит реальный DOM (elmToMove) на передний план реального DOM, соответствующего на старыйStartVnode.
Конечно, также возможно, что newStartVnode не может найти непротиворечивый ключ в старом узле VNode, или даже если ключ тот же самый, но не тот же Vnode, тогда createElm будет вызван для создания нового узла DOM.
На этом цикл закончен, поэтому нам все еще нужно иметь дело с избытком или недостатком реальных узлов DOM.
1. Когда oldStartIdx > oldEndIdx в конце, старый узел VNode был пройден, а новый узел еще не пройден. Это показывает, что новые узлы VNode на самом деле больше, чем старые узлы VNode, то есть больше, чем реальный DOM.Необходимо вставить оставшиеся (то есть вновь добавленные) узлы VNode в реальные узлы DOM.В это время , вызовите addVnodes (пакетный вызов интерфейса createElm для добавления этих узлов в настоящий DOM).
2. Точно так же, когда newStartIdx > newEndIdx, новый узел VNode был пройден, но старый узел все еще остался, что указывает на то, что реальный узел DOM является избыточным и должен быть удален из документа.В это время вызовите removeVnodes, чтобы удалить эти избыточные узлы. настоящие узлы DOM.
Манипуляции с DOM
Поскольку Vue использует виртуальный DOM, виртуальный DOM может работать на любой платформе, поддерживающей язык JavaScript, например, на платформе браузера или на платформе weex, которая в настоящее время поддерживается Vue, реализация виртуального DOM согласуется. Так как же в конечном итоге виртуальный DOM сопоставляется с реальным узлом DOM?
Vue сделал адаптационный слой для платформы, см. платформу браузера/platforms/web/runtime/node-ops.jsи платформу weex см./platforms/weex/runtime/node-ops.js. Различные платформы предоставляют один и тот же интерфейс наружу через уровень адаптации.Когда виртуальный DOM работает с реальным узлом DOM, ему нужно только вызвать интерфейс этих слоев адаптации, и внутреннюю реализацию не нужно беспокоить, она изменится. в зависимости от платформы.и изменить.
Теперь есть другая проблема, мы просто сопоставляем виртуальный DOM с реальным DOM. Как добавить в эти DOM атрибуты attr, class, style и другие атрибуты DOM?
Это зависит от жизненного крючка виртуального DOM. Виртуальный DOM предоставляет следующие функции ловушек, которые вызываются в разное время.
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
/*构建cbs回调函数,web平台上见/platforms/web/runtime/modules*/
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
Точно так же будут разные реализации для разных платформ.В качестве примера мы возьмем веб-платформу. Информацию о функциях ловушек веб-платформы см./platforms/web/runtime/modules.里面有对attr、class、props、events、style以及transition(过渡状态)的DOM属性进行操作。
Принимая привлечение в качестве примера, код очень прост.
/* @flow */
import { isIE9 } from 'core/util/env'
import {
extend,
isDef,
isUndef
} from 'shared/util'
import {
isXlink,
xlinkNS,
getXlinkProp,
isBooleanAttr,
isEnumeratedAttr,
isFalsyAttrValue
} from 'web/util/index'
/*更新attr*/
function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
/*如果旧的以及新的VNode节点均没有attr属性,则直接返回*/
if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {
return
}
let key, cur, old
/*VNode节点对应的Dom实例*/
const elm = vnode.elm
/*旧VNode节点的attr*/
const oldAttrs = oldVnode.data.attrs || {}
/*新VNode节点的attr*/
let attrs: any = vnode.data.attrs || {}
// clone observed objects, as the user probably wants to mutate it
/*如果新的VNode的attr已经有__ob__(代表已经被Observe处理过了), 进行深拷贝*/
if (isDef(attrs.__ob__)) {
attrs = vnode.data.attrs = extend({}, attrs)
}
/*遍历attr,不一致则替换*/
for (key in attrs) {
cur = attrs[key]
old = oldAttrs[key]
if (old !== cur) {
setAttr(elm, key, cur)
}
}
// #4391: in IE9, setting type can reset value for input[type=radio]
/* istanbul ignore if */
if (isIE9 && attrs.value !== oldAttrs.value) {
setAttr(elm, 'value', attrs.value)
}
for (key in oldAttrs) {
if (isUndef(attrs[key])) {
if (isXlink(key)) {
elm.removeAttributeNS(xlinkNS, getXlinkProp(key))
} else if (!isEnumeratedAttr(key)) {
elm.removeAttribute(key)
}
}
}
}
/*设置attr*/
function setAttr (el: Element, key: string, value: any) {
if (isBooleanAttr(key)) {
// set attribute for blank value
// e.g. <option disabled>Select one</option>
if (isFalsyAttrValue(value)) {
el.removeAttribute(key)
} else {
el.setAttribute(key, key)
}
} else if (isEnumeratedAttr(key)) {
el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')
} else if (isXlink(key)) {
if (isFalsyAttrValue(value)) {
el.removeAttributeNS(xlinkNS, getXlinkProp(key))
} else {
el.setAttributeNS(xlinkNS, key, value)
}
} else {
if (isFalsyAttrValue(value)) {
el.removeAttribute(key)
} else {
el.setAttribute(key, value)
}
}
}
export default {
create: updateAttrs,
update: updateAttrs
}
attr нужно только обновить атрибут attr DOM, когда вызываются хуки создания и обновления.
о
Автор: Ран Мо
Электронная почта: answerhuto@gmail.com или answerhuto@126.com
Github: github.com/answershuto
Знать столбец:zhuanlan.zhihu.com/ranmo
Самородки:Талант /user/289926…
Ос Китай:no.OSCHINA.net/U/3161824/no…
Пожалуйста, указывайте источник при перепечатке, спасибо.
Добро пожаловать, чтобы обратить внимание на мой общедоступный номер