Cet article analysera le Virtual Dom utilisé dans la version Vue 2.5.3. updataChildren est au c?ur de l'algorithme Diff, cet article effectue donc une analyse graphique de updataChildren. Permettez-moi de partager avec vous l'algorithme Diff de Vue 2.5 à travers cet article. Les amis qui en ont besoin peuvent s'y référer
DOM est "intrinsèquement lent", donc tous les principaux frameworks frontaux fournissent des moyens d'optimiser les opérations DOM. Dans Angular, il s'agit d'une vérification de valeur sale. React a d'abord proposé Virtual Dom, et Vue2.0 a également ajouté Virtual Dom, qui est similaire à React.
Cet article analysera le Virtual Dom utilisé dans la version Vue 2.5.3.
updataChildren est le c?ur de l'algorithme Diff, cet article effectue donc une analyse graphique de updataChildren.
Objet 1.VNode
Une instance de VNode contient les attributs suivants. Cette partie du code se trouve dans src/core/vdom/vnode. .js ri
export default 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; functionalContext: Component | void; // real context vm for functional nodes functionalOptions: ?ComponentOptions; // for SSR caching functionalScopeId: ?string; // functioanl scope id support
tag?: le nom de la balise du n?ud actuel
data?: l'objet de données du n?ud actuel S'il vous pla?t. reportez-vous à vue pour des champs spécifiques. La définition de VNodeData dans le code source types/vnode.d.ts
children?: type de tableau, y compris les n?uds enfants du n?ud actuel
text : Le texte du n?ud actuel. Généralement, les n?uds de texte ou les n?uds de commentaire auront cet attribut
elm : Le vrai n?ud dom correspondant au courant. n?ud virtuel
ns?: espace de noms du n?ud
contexte?: portée de compilation
fonctionnelContext?: portée de composant fonctionnel
key?: L'attribut clé du n?ud, utilisé comme identifiant du n?ud, qui est bénéfique pour l'optimisation des correctifs
composantOptions?: informations sur les options utilisées lors de la création d'une instance de composant
enfant?: l'instance de composant correspondant au n?ud actuel
parent?: le n?ud d'espace réservé de le composant
raw : raw html
isStatic : Identification du n?ud statique
isRootInsert : S'il faut insérer en tant que n?ud racine, par
isComment?:?si le n?ud actuel est un n?ud de commentaire
isCloned?: si le n?ud actuel est un n?ud clone
isOnce?: indique si le n?ud actuel a la directive v-once
2. ??>
VNode peut être compris comme une classe de base de VueVirtual Dom, les instances de VNnode générées via le constructeur de VNode peuvent appartenir aux catégories suivantes?:- EmptyVNode : n?ud de commentaire sans contenu
- TextVNode?: n?ud de texte
- ElementVNode?: n?ud d'élément commun
- ComponentVNode?: n?ud de composant
- CloneVNode?: cloner un n?ud, vous pouvez Il s'agit de n'importe quel type de n?ud ci-dessus. La seule différence est que l'attribut isCloned est vrai .
3.Analyse du code source de Create-Element
Cette partie du code est dans src/core/vdom/create-element.js, je viens de collez le code et ajoutez mes commentairesexport function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode { // 兼容不傳data的情況 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } // 如果alwaysNormalize是true // 那么normalizationType應(yīng)該設(shè)置為常量ALWAYS_NORMALIZE的值 if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } // 調(diào)用_createElement創(chuàng)建虛擬節(jié)點(diǎn) return _createElement(context, tag, data, children, normalizationType) } export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode { /** * 如果存在data.__ob__,說(shuō)明data是被Observer觀察的數(shù)據(jù) * 不能用作虛擬節(jié)點(diǎn)的data * 需要拋出警告,并返回一個(gè)空節(jié)點(diǎn) * * 被監(jiān)控的data不能被用作vnode渲染的數(shù)據(jù)的原因是: * data在vnode渲染過(guò)程中可能會(huì)被改變,這樣會(huì)觸發(fā)監(jiān)控,導(dǎo)致不符合預(yù)期的操作 */ if (isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // 當(dāng)組件的is屬性被設(shè)置為一個(gè)falsy的值 // Vue將不會(huì)知道要把這個(gè)組件渲染成什么 // 所以渲染一個(gè)空節(jié)點(diǎn) // in case of component :is set to falsy value return createEmptyVNode() } // key為非原始值警告 // warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ) } // 作用域插槽 // support single function children as default scoped slot if (Array.isArray(children) && typeof children[0] === 'function' ) { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } // 根據(jù)normalizationType的值,選擇不同的處理方法 if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns // 如果標(biāo)簽名是字符串類型 if (typeof tag === 'string') { let Ctor // 獲取標(biāo)簽的命名空間 ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) // 如果是保留標(biāo)簽 if (config.isReservedTag(tag)) { // platform built-in elements // 就創(chuàng)建這樣一個(gè)vnode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) // 如果不是保留字標(biāo)簽,嘗試從vm的components上查找是否有這個(gè)標(biāo)簽的定義 } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component // 如果找到,就創(chuàng)建虛擬組件節(jié)點(diǎn) vnode = createComponent(Ctor, data, context, children, tag) } else { // unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children // 兜底方案,創(chuàng)建一個(gè)正常的vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // 當(dāng)tag不是字符串的時(shí)候,我們認(rèn)為tag是組件的構(gòu)造類 // 所以直接創(chuàng)建 // direct component options / constructor vnode = createComponent(tag, data, context, children) } if (isDef(vnode)) { // 應(yīng)用命名空間 if (ns) applyNS(vnode, ns) return vnode } else { // 返回一個(gè)空節(jié)點(diǎn) return createEmptyVNode() } } function applyNS (vnode, ns, force) { vnode.ns = ns if (vnode.tag === 'foreignObject') { // use default namespace inside foreignObject ns = undefined force = true } if (isDef(vnode.children)) { for (let i = 0, l = vnode.children.length; i < l; i++) { const child = vnode.children[i] if (isDef(child.tag) && (isUndef(child.ns) || isTrue(force))) { applyNS(child, ns, force) } } } }
4. Principe du patch
La fonction patch est définie dans src/core/vdom/. patch.js. La logique du patch est relativement simple et le code n'est pas collantLa fonction patch re?oit 6 paramètres?:
- oldVnode?: ancien n?ud virtuel ou ancien n?ud dom réel
- vnode : nouveau n?ud virtuel
- hydratant : s'il faut le mélanger avec du vrai dom
- removeOnly?: drapeau spécial, utilisé pour
- parentElm?: N?ud parent
- refElm?: Le nouveau n?ud sera inséré avant refElm
La logique du patch est?:
Si vnode n'existe pas mais que oldVnode existe, cela signifie que l'intention est de détruire l'ancien n?ud, puis appelez InvokeDestroyHook(oldVnode) pour le détruire Si oldVnode n'existe pas mais que vnode existe, cela signifie que l'intention est de créer un nouveau n?ud, puis appelez createElm pour créer un nouveau n?udsinon Lorsque vnode et oldVnode existentSi oldVnode et vnode sont le même n?ud, appelez patchVnode pour patch Lorsque vnode et oldVnode ne sont pas le même n?ud, si oldVnode est un Le n?ud dom réel ou hydratant est défini sur true, vous devez utiliser la fonction hydrate pour mapper le dom virtuel et le dom réel, puis définir oldVnode sur le dom virtuel correspondant et trouver le n?ud parent oldVnode.elm, créer un n?ud dom réel basé sur sur vnode et insérez-le dans le n?ud parent à la position de oldVnode.elmLa logique de patchVnode est?:
1 Si oldVnode suit Les vnodes sont complètement cohérents. , vous n'avez donc rien à faire 2. Si oldVnode et vnode sont tous deux des n?uds statiques et ont la même clé, lorsque vnode est un n?ud clone ou un n?ud contr?lé par la commande v-once, uniquement Vous devez copier à la fois oldVnode.elm et oldVnode.child dans vnode, et aucune autre opération n'est requise 3 Sinon, si vnode n'est pas un n?ud de texte ou un n?ud d'annotation- .
- Si oldVnode et vnode ont tous deux des n?uds enfants et que les n?uds enfants des deux parties ne sont pas complètement cohérents, exécutez updateChildren
- Si seul oldVnode a des n?uds enfants, supprimez ces n?uds
- Si seulement vnode a des n?uds enfants, alors créez ces n?uds enfants
- Si ni oldVnode ni vnode n'ont de n?uds enfants, mais que oldVnode est un texte node Ou n?ud de commentaire, définissez le texte de vnode.elm sur la cha?ne vide
4.如果vnode是文本節(jié)點(diǎn)或注釋節(jié)點(diǎn),但是vnode.text != oldVnode.text時(shí),只需要更新vnode.elm的文本內(nèi)容就可以
代碼如下:
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { // 如果新舊節(jié)點(diǎn)一致,什么都不做 if (oldVnode === vnode) { return } // 讓vnode.el引用到現(xiàn)在的真實(shí)dom,當(dāng)el修改時(shí),vnode.el會(huì)同步變化 const elm = vnode.elm = oldVnode.elm // 異步占位符 if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } 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. // 如果新舊都是靜態(tài)節(jié)點(diǎn),并且具有相同的key // 當(dāng)vnode是克隆節(jié)點(diǎn)或是v-once指令控制的節(jié)點(diǎn)時(shí),只需要把oldVnode.elm和oldVnode.child都復(fù)制到vnode上 // 也不用再有其他操作 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不是文本節(jié)點(diǎn)或者注釋節(jié)點(diǎn) if (isUndef(vnode.text)) { // 并且都有子節(jié)點(diǎn) if (isDef(oldCh) && isDef(ch)) { // 并且子節(jié)點(diǎn)不完全一致,則調(diào)用updateChildren if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) // 如果只有新的vnode有子節(jié)點(diǎn) } else if (isDef(ch)) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') // elm已經(jīng)引用了老的dom節(jié)點(diǎn),在老的dom節(jié)點(diǎn)上添加子節(jié)點(diǎn) addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) // 如果新vnode沒(méi)有子節(jié)點(diǎn),而vnode有子節(jié)點(diǎn),直接刪除老的oldCh } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) // 如果老節(jié)點(diǎn)是文本節(jié)點(diǎn) } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } // 如果新vnode和老vnode是文本節(jié)點(diǎn)或注釋節(jié)點(diǎn) // 但是vnode.text != oldVnode.text時(shí),只需要更新vnode.elm的文本內(nèi)容就可以 } 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) } }
5.updataChildren原理
updateChildren的邏輯是:
分別獲取oldVnode和vnode的firstChild、lastChild,賦值給oldStartVnode、oldEndVnode、newStartVnode、newEndVnode
如果oldStartVnode和newStartVnode是同一節(jié)點(diǎn),調(diào)用patchVnode進(jìn)行patch,然后將oldStartVnode和newStartVnode都設(shè)置為下一個(gè)子節(jié)點(diǎn),
如果oldEndVnode和newEndVnode是同一節(jié)點(diǎn),調(diào)用patchVnode進(jìn)行patch,然后將oldEndVnode和newEndVnode都設(shè)置為上一個(gè)子節(jié)點(diǎn),重復(fù)上述流程
如果oldStartVnode和newEndVnode是同一節(jié)點(diǎn),調(diào)用patchVnode進(jìn)行patch,如果removeOnly是false,那么可以把oldStartVnode.elm移動(dòng)到oldEndVnode.elm之后,然后把oldStartVnode設(shè)置為下一個(gè)節(jié)點(diǎn),newEndVnode設(shè)置為上一個(gè)節(jié)點(diǎn),重復(fù)上述流程
如果newStartVnode和oldEndVnode是同一節(jié)點(diǎn),調(diào)用patchVnode進(jìn)行patch,如果removeOnly是false,那么可以把oldEndVnode.elm移動(dòng)到oldStartVnode.elm之前,然后把newStartVnode設(shè)置為下一個(gè)節(jié)點(diǎn),oldEndVnode設(shè)置為上一個(gè)節(jié)點(diǎn),重復(fù)上述流程
如果以上都不匹配,就嘗試在oldChildren中尋找跟newStartVnode具有相同key的節(jié)點(diǎn),如果找不到相同key的節(jié)點(diǎn),說(shuō)明newStartVnode是一個(gè)新節(jié)點(diǎn),就創(chuàng)建一個(gè),然后把newStartVnode設(shè)置為下一個(gè)節(jié)點(diǎn)
如果上一步找到了跟newStartVnode相同key的節(jié)點(diǎn),那么通過(guò)其他屬性的比較來(lái)判斷這2個(gè)節(jié)點(diǎn)是否是同一個(gè)節(jié)點(diǎn),如果是,就調(diào)用patchVnode進(jìn)行patch,如果removeOnly是false,就把newStartVnode.elm插入到oldStartVnode.elm之前,把newStartVnode設(shè)置為下一個(gè)節(jié)點(diǎn),重復(fù)上述流程
如果在oldChildren中沒(méi)有尋找到newStartVnode的同一節(jié)點(diǎn),那就創(chuàng)建一個(gè)新節(jié)點(diǎn),把newStartVnode設(shè)置為下一個(gè)節(jié)點(diǎn),重復(fù)上述流程
如果oldStartVnode跟oldEndVnode重合了,并且newStartVnode跟newEndVnode也重合了,這個(gè)循環(huán)就結(jié)束了
具體代碼如下:
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { let oldStartIdx = 0 // 舊頭索引 let newStartIdx = 0 // 新頭索引 let oldEndIdx = oldCh.length - 1 // 舊尾索引 let newEndIdx = newCh.length - 1 // 新尾索引 let oldStartVnode = oldCh[0] // oldVnode的第一個(gè)child let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最后一個(gè)child let newStartVnode = newCh[0] // newVnode的第一個(gè)child let newEndVnode = newCh[newEndIdx] // newVnode的最后一個(gè)child let oldKeyToIdx, idxInOld, vnodeToMove, 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 // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,證明diff完了,循環(huán)結(jié)束 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // 如果oldVnode的第一個(gè)child不存在 if (isUndef(oldStartVnode)) { // oldStart索引右移 oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left // 如果oldVnode的最后一個(gè)child不存在 } else if (isUndef(oldEndVnode)) { // oldEnd索引左移 oldEndVnode = oldCh[--oldEndIdx] // oldStartVnode和newStartVnode是同一個(gè)節(jié)點(diǎn) } else if (sameVnode(oldStartVnode, newStartVnode)) { // patch oldStartVnode和newStartVnode, 索引左移,繼續(xù)循環(huán) patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] // oldEndVnode和newEndVnode是同一個(gè)節(jié)點(diǎn) } else if (sameVnode(oldEndVnode, newEndVnode)) { // patch oldEndVnode和newEndVnode,索引右移,繼續(xù)循環(huán) patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] // oldStartVnode和newEndVnode是同一個(gè)節(jié)點(diǎn) } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right // patch oldStartVnode和newEndVnode patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) // 如果removeOnly是false,則將oldStartVnode.eml移動(dòng)到oldEndVnode.elm之后 canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // oldStart索引右移,newEnd索引左移 oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] // 如果oldEndVnode和newStartVnode是同一個(gè)節(jié)點(diǎn) } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left // patch oldEndVnode和newStartVnode patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) // 如果removeOnly是false,則將oldEndVnode.elm移動(dòng)到oldStartVnode.elm之前 canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) // oldEnd索引左移,newStart索引右移 oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] // 如果都不匹配 } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 嘗試在oldChildren中尋找和newStartVnode的具有相同的key的Vnode idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 如果未找到,說(shuō)明newStartVnode是一個(gè)新的節(jié)點(diǎn) if (isUndef(idxInOld)) { // New element // 創(chuàng)建一個(gè)新Vnode createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) // 如果找到了和newStartVnodej具有相同的key的Vnode,叫vnodeToMove } else { vnodeToMove = oldCh[idxInOld] /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !vnodeToMove) { warn( 'It seems there are duplicate keys that is causing an update error. ' + 'Make sure each v-for item has a unique key.' ) } // 比較兩個(gè)具有相同的key的新節(jié)點(diǎn)是否是同一個(gè)節(jié)點(diǎn) //不設(shè)key,newCh和oldCh只會(huì)進(jìn)行頭尾兩端的相互比較,設(shè)key后,除了頭尾兩端的比較外,還會(huì)從用key生成的對(duì)象oldKeyToIdx中查找匹配的節(jié)點(diǎn),所以為節(jié)點(diǎn)設(shè)置key可以更高效的利用dom。 if (sameVnode(vnodeToMove, newStartVnode)) { // patch vnodeToMove和newStartVnode patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) // 清除 oldCh[idxInOld] = undefined // 如果removeOnly是false,則將找到的和newStartVnodej具有相同的key的Vnode,叫vnodeToMove.elm // 移動(dòng)到oldStartVnode.elm之前 canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) // 如果key相同,但是節(jié)點(diǎn)不相同,則創(chuàng)建一個(gè)新的節(jié)點(diǎn) } else { // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) } } // 右移 newStartVnode = newCh[++newStartIdx] } }
6.具體的Diff分析
不設(shè)key,newCh和oldCh只會(huì)進(jìn)行頭尾兩端的相互比較,設(shè)key后,除了頭尾兩端的比較外,還會(huì)從用key生成的對(duì)象oldKeyToIdx中查找匹配的節(jié)點(diǎn),所以為節(jié)點(diǎn)設(shè)置key可以更高效的利用dom。
diff的遍歷過(guò)程中,只要是對(duì)dom進(jìn)行的操作都調(diào)用api.insertBefore,api.insertBefore只是原生insertBefore的簡(jiǎn)單封裝。
比較分為兩種,一種是有vnode.key的,一種是沒(méi)有的。但這兩種比較對(duì)真實(shí)dom的操作是一致的。
對(duì)于與sameVnode(oldStartVnode, newStartVnode)和sameVnode(oldEndVnode,newEndVnode)為true的情況,不需要對(duì)dom進(jìn)行移動(dòng)。
總結(jié)遍歷過(guò)程,有3種dom操作:上述圖中都有
1.當(dāng)oldStartVnode,newEndVnode值得比較,說(shuō)明oldStartVnode.el跑到oldEndVnode.el的后邊了。
2.當(dāng)oldEndVnode,newStartVnode值得比較,oldEndVnode.el跑到了oldStartVnode.el的前邊,準(zhǔn)確的說(shuō)應(yīng)該是oldEndVnode.el需要移動(dòng)到oldStartVnode.el的前邊”。
3.newCh中的節(jié)點(diǎn)oldCh里沒(méi)有, 將新節(jié)點(diǎn)插入到oldStartVnode.el的前邊
在結(jié)束時(shí),分為兩種情況:
1.oldStartIdx > oldEndIdx,可以認(rèn)為oldCh先遍歷完。當(dāng)然也有可能newCh此時(shí)也正好完成了遍歷,統(tǒng)一都?xì)w為此類。此時(shí)newStartIdx和newEndIdx之間的vnode是新增的,調(diào)用addVnodes,把他們?nèi)坎暹M(jìn)before的后邊,before很多時(shí)候是為null的。addVnodes調(diào)用的是insertBefore操作dom節(jié)點(diǎn),我們看看insertBefore的文檔:parentElement.insertBefore(newElement, referenceElement)
如果referenceElement為null則newElement將被插入到子節(jié)點(diǎn)的末尾。如果newElement已經(jīng)在DOM樹(shù)中,newElement首先會(huì)從DOM樹(shù)中移除。所以before為null,newElement將被插入到子節(jié)點(diǎn)的末尾。
2.newStartIdx > newEndIdx, on peut considérer que newCh est parcouru en premier. Pour le moment, le vnode entre oldStartIdx et oldEndIdx n'existe plus dans le nouveau n?ud enfant. Appelez RemoveVnodes pour les supprimer du dom
Ce qui précède est ce que j'ai compilé pour tout le monde. J'espère que cela sera utile à tout le monde. à l'avenir.
Articles associés?:
Chrome Firefox est livré avec des outils de débogage (tutoriel détaillé)
à propos de la fa?on dont Vue.js implémente le chargement par défilement infini
Comment implémenter le filtrage de tableaux à l'aide d'Angular
Comment implémenter une calculatrice à l'aide de JavaScript
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undress AI Tool
Images de déshabillage gratuites

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
échangez les visages dans n'importe quelle vidéo sans effort grace à notre outil d'échange de visage AI entièrement gratuit?!

Article chaud

Outils chauds

Bloc-notes++7.3.1
éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

ReactivitytransforminVue3aimedtosimplifyhandlingreactivedatabyautomaticallytrackingandmanagingreactivitywithoutrequiringmanualref()or.valueusage.Itsoughttoreduceboilerplateandimprovecodereadabilitybytreatingvariableslikeletandconstasautomaticallyreac

AlgorithmsInpyThonareEssentialForFicientProblem-SolvingInProgramming.

Internationalisation et olocalisation dans la variation des acquis

Server-sideredering (SSR) invueImproveSperformanceAndSeoBygeneratingHtmlONTheServer.1.TheServerrunsvueAppcodeandGenerateshtmlBaseDonthecurrentRoute.2.ThathtmLissentToHebroweToWeTerterActive.

La construction d'une bibliothèque de composants Vue nécessite la conception de la structure autour du scénario d'entreprise et le suivi du processus complet de développement, de test et de libération. 1. La conception structurelle doit être classée en fonction des modules fonctionnels, y compris des composants de base, des composants de mise en page et des composants commerciaux; 2. Utilisez des variables SCSS ou CSS pour unifier le thème et le style; 3. Unifier les spécifications de dénomination et introduire Eslint et plus joli pour assurer le style de code cohérent; 4. Afficher l'utilisation des composants sur le site de document de support; 5. Utilisez VITE et d'autres outils pour emballer en tant que packages NPM et configurer les rolupoptions; 6. Suivez la spécification SEMVER pour gérer les versions et les modifications modifiées lors de la publication.

ToaddtransitionsandanimationsInvue, usebuilt-incomponentslikeandand, applatcsclasses, leveragetransitionhooksforControl, andoptimezeperformance.1.wrapelementswithandapplycsstransitionclasses lisev-enter-actinterforbasicfadeorslideeffets.2.

NextTick est utilisé dans Vue pour exécuter du code après la mise à jour DOM. Lorsque les données changent, Vue ne mettra pas à jour le DOM immédiatement, mais le mettra dans la file d'attente et le traitera dans la prochaine boucle d'événement "Tick". Par conséquent, si vous devez accéder ou exploiter le DOM mis à jour, NextTick doit être utilisé; Les scénarios courants incluent: l'accès au contenu DOM mis à jour, la collaboration avec des bibliothèques tierces qui s'appuient sur l'état DOM et le calcul en fonction de la taille de l'élément; Son utilisation comprend l'appel. $ NextTick comme méthode de composant, l'utiliser seul après l'importation et la combinaison asynchrone / attendre; Les précautions comprennent: éviter une utilisation excessive, dans la plupart des cas, aucun déclenchement manuel n'est requis, et un prochain peut capturer plusieurs mises à jour à la fois.

1. Le premier choix pour la combinaison Laravel Mysql Vue / React dans la communauté de questions et réponses de développement PHP est le premier choix pour la combinaison Laravel Mysql Vue / React, en raison de sa maturité dans l'écosystème et de l'efficacité de développement élevée; 2. Les performances élevées nécessitent une dépendance à la cache (redis), une optimisation de la base de données, des files d'attente CDN et asynchrones; 3. La sécurité doit être effectuée avec le filtrage d'entrée, la protection CSRF, les HTTP, le cryptage de mot de passe et le contr?le d'autorisation; 4. Publicité facultative, abonnement aux membres, récompenses, commissions, paiement des connaissances et autres modèles, le noyau est de faire correspondre le ton communautaire et les besoins des utilisateurs.
