Vue核心虚拟Dom和 diff 算法
一、介绍虚拟DOM
什么是虚拟DOM?
之前我的理解:
虚拟DOM是一个真实DOM的映射,Vue是拿虚拟DOM描述真实DOM,虚拟DOM体积比真实DOM小,每次操作虚拟DOM都会触发重排,判断虚拟DOM发生的变动Vue从而渲染真实DOM。除了能提高性能上的好处之外虚拟DOM还可以与真实DOM解耦,使Vue可以不依赖真实DOM,操作更自由。
虚拟DOM就是通过JS来生成一个AST节点树
为什么要有虚拟DOM?
let div = document.createElement('div') let str = '' for (const key in div) { str += key + '' } console.log(str)
发现一个dom上面的属性是非常多的
aligntitlelangtranslatedirhiddenaccessKeydraggablespellcheckautocapitalizecontentEditableisContentEditableinputModeoffsetParentoffsetTopoffsetLeftoffsetWidthoffsetHeightstyleinnerTextouterTextonbeforexrselectonabortonbluroncanceloncanplayoncanplaythroughonchangeonclickoncloseoncontextmenuoncuechangeondblclickondragondragendondragenterondragleaveondragoverondragstartondropondurationchangeonemptiedonendedonerroronfocusonformdataoninputoninvalidonkeydownonkeypressonkeyuponloadonloadeddataonloadedmetadataonloadstartonmousedownonmouseenteronmouseleaveonmousemoveonmouseoutonmouseoveronmouseuponmousewheelonpauseonplayonplayingonprogressonratechangeonresetonresizeonscrollonsecuritypolicyviolationonseekedonseekingonselectonslotchangeonstalledonsubmitonsuspendontimeupdateontoggleonvolumechangeonwaitingonwebkitanimationendonwebkitanimationiterationonwebkitanimationstartonwebkittransitionendonwheelonauxclickongotpointercaptureonlostpointercaptureonpointerdownonpointermoveonpointeruponpointercancelonpointeroveronpointeroutonpointerenteronpointerleaveonselectstartonselectionchangeonanimationendonanimationiterationonanimationstartontransitionrunontransitionstartontransitionendontransitioncanceloncopyoncutonpastedatasetnonceautofocustabIndexattachInternalsblurclickfocusenterKeyHintvirtualKeyboardPolicyonpointerrawupdatenamespaceURIprefixlocalNametagNameidclassNameclassListslotattributesshadowRootpartassignedSlotinnerHTMLouterHTMLscrollTopscrollLeftscrollWidthscrollHeightclientTopclientLeftclientWidthclientHeightattributeStyleMaponbeforecopyonbeforecutonbeforepasteonsearchelementTimingonfullscreenchangeonfullscreenerroronwebkitfullscreenchangeonwebkitfullscreenerrorchildrenfirstElementChildlastElementChildchildElementCountpreviousElementSiblingnextElementSiblingafteranimateappendattachShadowbeforeclosestcomputedStyleMapgetAttributegetAttributeNSgetAttributeNamesgetAttributeNodegetAttributeNodeNSgetBoundingClientRectgetClientRectsgetElementsByClassNamegetElementsByTagNamegetElementsByTagNameNSgetInnerHTMLhasAttributehasAttributeNShasAttributeshasPointerCaptureinsertAdjacentElementinsertAdjacentHTMLinsertAdjacentTextmatchesprependquerySelectorquerySelectorAllreleasePointerCaptureremoveremoveAttributeremoveAttributeNSremoveAttributeNodereplaceChildrenreplaceWithrequestFullscreenrequestPointerLockscrollscrollByscrollIntoViewscrollIntoViewIfNeededscrollTosetAttributesetAttributeNSsetAttributeNodesetAttributeNodeNSsetPointerCapturetoggleAttributewebkitMatchesSelectorwebkitRequestFullScreenwebkitRequestFullscreenariaAtomicariaAutoCompleteariaBusyariaCheckedariaColCountariaColIndexariaColSpanariaCurrentariaDescriptionariaDisabledariaExpandedariaHasPopupariaHiddenariaKeyShortcutsariaLabelariaLevelariaLiveariaModalariaMultiLineariaMultiSelectableariaOrientationariaPlaceholderariaPosInSetariaPressedariaReadOnlyariaRelevantariaRequiredariaRoleDescriptionariaRowCountariaRowIndexariaRowSpanariaSelectedariaSetSizeariaSortariaValueMaxariaValueMinariaValueNowariaValueTextgetAnimationsnodeTypenodeNamebaseURIisConnectedownerDocumentparentNodeparentElementchildNodesfirstChildlastChildpreviousSiblingnextSiblingnodeValuetextContentELEMENT_NODEATTRIBUTE_NODETEXT_NODECDATA_SECTION_NODEENTITY_REFERENCE_NODEENTITY_NODEPROCESSING_INSTRUCTION_NODECOMMENT_NODEDOCUMENT_NODEDOCUMENT_TYPE_NODEDOCUMENT_FRAGMENT_NODENOTATION_NODEDOCUMENT_POSITION_DISCONNECTEDDOCUMENT_POSITION_PRECEDINGDOCUMENT_POSITION_FOLLOWINGDOCUMENT_POSITION_CONTAINSDOCUMENT_POSITION_CONTAINED_BYDOCUMENT_POSITION_IMPLEMENTATION_SPECIFICappendChildcloneNodecompareDocumentPositioncontainsgetRootNodehasChildNodesinsertBeforeisDefaultNamespaceisEqualNodeisSameNodelookupNamespaceURIlookupPrefixnormalizeremoveChildreplaceChildaddEventListenerdispatchEventremoveEventListener
所以直接操作DOM非常浪费性能
解决方案就是 我们可以用JS
的计算性能来换取操作DOM
所消耗的性能,既然我们逃不掉操作DOM
这道坎,但是我们可以尽可能少的操作DOM
操作JS是非常快的,与真实DOM解耦,Vue可以不依赖真实DOM,操作更自由
二、介绍Diff算法
Vue3 源码地址 https://github.com/vuejs/core
<template> <div> <div :key="item" v-for="(item) in Arr">{{ item }}</div> </div> </template> <script setup lang="ts"> const Arr: Array<string> = ['a', 'B', 'C', 'D'] Arr.splice(2,0,'DDD') </script> <style> </style>
总结:
1.没有Key的diff算法只有三步骤:
首先进行新旧的VNode进行对比,新的会把旧的替换掉,如果发现有多的会进行第二步骤插入,如果发现少了会进性删除
2.有Key的diff算法分为五个步骤:
首先进性前序算法如果对比过程中出现不一样会break跳出循环,然后进性尾序对比从后面开始对比发现不一样也会跳出循环与vue2的双端diff算法不同,vue2的diff算法是头和头尾和尾,然后头和尾尾和头,这样子交叉对比,vue3没有头尾交叉对比,vue3做了优化最长递增子序列算法。头尾比完之后发现多出来的会进行第三步patch新增,如若发现少了的会进行第四步骤卸载unmount。最难的是在第五步乱序有可能是做了位移有可能是做了新增,也可能删除新增位移同时发生很多一些不可控的情况。在第五步底层源码中是做了三个部分,构建新节点映射关系、纪录新节点在旧的节点中的位置数组,如果有多余的旧节点就给他删掉,如果新节点不包含的旧节点也给他删掉,如果节点出现交叉,说明是要移动了,要去求最长子序列算法。求出来之后如果当前遍历的这个节点不在子序列中要进行移动,如果在子序列中就跳过。