A long time ago, I wrote an article analyzing and implementing a simple version of VDOM. If you want to see it, click on the portal
Talk about why you want to write such an article, it is in the project, no matter yourself or colleagues, more or less will encounter this pit. So when here to do a summary for friends, I hope everyone to read, can have a better understanding of VDOM in VUE. All right, let’s just get started
First, ask questions
Before we begin, LET me pose a question for you to think about and then read the rest of the book. So let’s go up and down
<template>
<el-select
class="test-select"
multiple
filterable
remote
placeholder="Please enter keywords"
:remote-method="remoteMethod"
:loading="loading"
@focus="handleFoucs"
v-model="items">
<! Vfor index -->
<el-option
v-for="(item, index) in options"
:key="index"
:label="item.label"
:value="item.value">
<el-checkbox
:label="item.value"
:value="isChecked(item.value)">
{{ item.label }}
</el-checkbox>
</el-option>
</el-select>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class TestSelect extends Vue {
options: ArrayThe < {label: string, value: string }> = []
items: Array<string> = []
list: ArrayThe < {label: string, value: string }> = []
loading: boolean = false
states = ['Alabama'.'Alaska'.'Arizona'.'Arkansas'.'California'.'Colorado'.'Connecticut'.'Delaware'.'Florida'.'Georgia'.'Hawaii'.'Idaho'.'Illinois'.'Indiana'.'Iowa'.'Kansas'.'Kentucky'.'Louisiana'.'Maine'.'Maryland'.'Massachusetts'.'Michigan'.'Minnesota'.'Mississippi'.'Missouri'.'Montana'.'Nebraska'.'Nevada'.'New Hampshire'.'New Jersey'.'New Mexico'.'New York'.'North Carolina'.'North Dakota'.'Ohio'.'Oklahoma'.'Oregon'.'Pennsylvania'.'Rhode Island'.'South Carolina'.'South Dakota'.'Tennessee'.'Texas'.'Utah'.'Vermont'.'Virginia'.'Washington'.'West Virginia'.'Wisconsin'.'Wyoming']
mounted () {
this.list = this.states.map(item= > {
return { value: item, label: item }
})
}
remoteMethod (query) {
if(query ! = =' ') {
this.loading = true
setTimeout((a)= > {
this.loading = false
this.options = this.list.filter(item= > {
return item.label.toLowerCase()
.indexOf(query.toLowerCase()) > - 1})},200)}else {
this.options = this.list
}
}
handleFoucs (e) {
this.remoteMethod(e.target.value)
}
isChecked (value: string): boolean {
let checked = false
this.items.forEach((item: string) = > {
if (item === value) {
checked = true}})return checked
}
}
</script>
Copy the code
The effect picture after input screening is as follows
Then I search for another keyword, and the results will show the following problem
I didn’t make the selection, but the values displayed in the Select box have changed. Veteran drivers may know the problem as soon as they look at the code. In fact, replace the key binding in option with the following
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
<el-checkbox
:label="item.value"
:value="isChecked(item.value)">
{{ item.label }}
</el-checkbox>
</el-option>
Copy the code
So the question is, you can avoid it, but why can you avoid it? In fact, this part involves patch related content in VDOM. I’m going to take you back and pick up the VDOM again
Before we begin, take a look at a few apis that come up frequently in this article
isDef()
export function isDef (v: any): boolean %checks { return v ! == undefined && v ! == null }Copy the code
isUndef()
export function isUndef (v: any): boolean %checks {
return v === undefined || v === null
}
Copy the code
isTrue()
export function isTrue (v: any): boolean %checks {
return v === true
}
Copy the code
Second, the class VNode
Before we start, let’s talk about VNode. The VDOM in VUE is actually a VNode object.
For those of you who know a little about VDOM, the core of node creation in VDOM is to create a TREE of JS objects abstracted from the real DOM, followed by a series of operations (more on that later). In this section we will only talk about vNode implementation
1, the constructor
First of all, we can take a look at the VNode class to us those users exposed to what attributes out, pick some of our common look
constructor( tag? : string, data? : VNodeData, children? :? Array<VNode>, text? : string, elm? : Node, context? : Component ) {this.tag = tag // Label name of the node
this.data = data // Data information of the node, such as props, attrs, key, class, and directives
this.children = children // The child node of the node
this.text = text // The text corresponding to the node
this.elm = elm // The real node corresponding to the node
this.context = context // Node context, which is the definition of Vue Component
this.key = data && data.key // The node is used as a unique identifier for diff
}
Copy the code
2, for example
Now, for example, suppose I need to parse the following text
<template>
<div class="vnode" :class={ 'show-node': isShow } v-show="isShow">
This is a vnode.
</div>
</template>
Copy the code
This is how abstraction works with JS
Function render () {return VNode('div', {// staticClass: 'VNode ', // staticClass: {'show-node': isShow }, /** * directives: [ * { * rawName: 'v-show', * name: 'show', * value: IsShow *} *], */ // equivalent to the V-show directives: isShow, [ new VNode(undefined, undefined, undefined, 'This is a vnode.') ] } ) }Copy the code
After being converted to a VNode, the format is as follows
{tag: 'div', data: {show: isShow, // staticClass: 'vnode', // staticClass: {'show-node': isShow }, }, text: undefined, children: [ { tag: undefined, data: undefined, text: 'This is a vnode.', children: undefined } ] }Copy the code
And then LET me do a slightly more complicated example
<span v-for="n in 5" :key="n">{{ n }}</span>
Copy the code
If let everybody use JS to carry on the object abstraction to it, how will everybody proceed? Mainly the inside of the V-for instruction, you can first take your own thinking to try.
OK, not to be kept in mind, let’s take a look at the render function abstraction below, which is actually the render loop.
function render (val, keyOrIndex, index) {
return new VNode(
'span',
{
directives: [{rawName: 'v-for'.name: 'for'.value: val
}
],
key: val,
[ new VNode(undefined.undefined.undefined, val) ]
}
)
}
function renderList (val: any, render: ( val: any, keyOrIndex: string | number, index? : number) = >VNode
): ?Array<VNode> {
// Just consider the number case
let ret: ?Array<VNode>, i, l, keys, key
ret = new Array(val)
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i)
}
return ret
}
renderList(5)
Copy the code
After being converted to a VNode, the format is as follows
[{tag: 'span'.data: {
key: 1
},
text: undefined.children: [{tag: undefined.data: undefined.text: 1.children: undefined}}]// loop through
]
Copy the code
3, something else
We’ve looked at some of the properties of VNode Ctor and looked at the conversion form for real DOM VNodes. Here we’ll fill in the gaps and look at some of the methods exposed by some of the encapsulation done based on VNode
// Create an empty node
export const createEmptyVNode = (text: string = ' ') = > {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
// Create a text node
export function createTextVNode (val: string | number) {
return new VNode(undefined.undefined.undefined.String(val))
}
// Clone a node, listing only some attributes
export function cloneVNode (vnode: VNode) :VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
vnode.children,
vnode.text
)
cloned.key = vnode.key
cloned.isCloned = true
return cloned
}
Copy the code
In the following sections, we will see how Vue renders a VNode into a real DOM
Third, render
1, the createElement method
Before we look at the implementation of createElement in vue, let’s look at the implementation of the private _createElement method in the same file. There are some logical decisions about tag
- TagName is bound to the data parameter
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
Copy the code
- If tagName does not exist, an empty node is returned
if(! tag) {return createEmptyVNode()
}
Copy the code
- If the tagName type is string, the vNode object corresponding to the tag is returned
vnode = new VNode(
tag, data, children,
undefined.undefined, context
)
Copy the code
- If tagName is not a string, the command is executed
createComponent()
To create aComponentobject
vnode = createComponent(tag, data, context, children)
Copy the code
- Determine the vNode type and return the corresponding vNode
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
// Related processing of namespace
if (isDef(ns)) applyNS(vnode, ns)
// Perform Observer binding
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
Copy the code
CreateElement () returns vNode with _createElement()
return _createElement(context, tag, data, children, normalizationType)
Copy the code
2, render functions provides
i. renderHelpers
Here we’ll take a look at the render related methods mounted on Vue.prototype
export function installRenderHelpers (target: any) {
target._o = markOnce // v-once render
target._n = toNumber // Value conversion Number processing
target._s = toString // Value conversion String processing
target._l = renderList // v_for render
target._t = renderSlot // slot point render processing
target._q = looseEqual // Determine whether two objects are roughly equal
target._i = looseIndexOf // Peer attribute index, returns -1 if not present
target._m = renderStatic // Static node render processing
target._f = resolveFilter // the filters command render processing
target._k = checkKeyCodes // checking keyCodes from config
target._b = bindObjectProps // merge v-bind="object" into VNode
target._v = createTextVNode // Create a text node
target._e = createEmptyVNode // Create an empty node
target._u = resolveScopedSlots // scopeSlots render
target._g = bindObjectListeners // v-on render
}
Copy the code
Then init vue.prototype in the renderMixin() method
export function renderMixin (Vue: Class<Component>) {
// render helps init
installRenderHelpers(Vue.prototype)
// Define the vue nextTick method
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function () :VNode {
// Define the VM instance here, and return vnode. The code is ignored here}}Copy the code
AST Abstract syntax tree
So far, all the render related operations we’ve seen have returned a VNode object, whereas before rendering the actual node, Vue parses the string in the template template and converts it into an AST abstract syntax tree for subsequent operations. We’ll look at the ASTElement interface type defined in vue for flow. We’ll just look at ASTElement’s definition for flow. After reading, remember to understand other definitions in the source code:
declare type ASTElement = {
tag: string; / / tag name
attrsMap: { [key: string]: any }; // Label attribute map
parent: ASTElement | void; / / parent tag
children: Array<ASTNode>; / / child nodes
for? : string;// The object being v-forforProcessed? : boolean;// Whether v-for needs to be processedkey? : string;// The key value of v-foralias? : string;// The v-for argumentiterator1? : string;// v-for for the first argumentiterator2? : string;// v-for for the second argument
};
Copy the code
Iii. Generate string conversion
renderList
Before looking at the Render Function string conversion, take a look at the renderList parameters for later reading
export function renderList (val: any, render: ( val: any, keyOrIndex: string | number, index? : number) = >VNode
): ?Array<VNode> {
// Render number (); // Render number ()
}
Copy the code
genFor
Generate converts an AST into a render function string. In the same way, we can do v-for-related processing
function genFor (el: any, state: CodegenState, altGen? : Function, altHelper? : string) :string {
const exp = el.for // The object of v-for
const alias = el.alias // The v-for argument
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ' ' // v-for for the first argument
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ' ' // v-for for the second argument
el.forProcessed = true // The instruction needs to be processed
// return the corresponding render function string
return `${altHelper || '_l'}((${exp}), ` +
`function(${alias}${iterator1}${iterator2}) {` +
`return ${(altGen || genElement)(el, state)}` +
'}) '
}
Copy the code
genElement
This piece integrates the conversion logic corresponding to each instruction
export function genElement (el: ASTElement, state: CodegenState) :string {
if(el.staticRoot && ! el.staticProcessed) {// Static node
return genStatic(el, state)
} else if(el.once && ! el.onceProcessed) {/ / v - once processing
return genOnce(el, state)
} else if(el.for && ! el.forProcessed) {/ / v - for processing
return genFor(el, state)
} else if(el.if && ! el.ifProcessed) {/ / v - the if processing
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) { // Template root node processing
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') { // slot node processing
return genSlot(el, state)
} else {
// Component or element processing}}Copy the code
generate
Generate integrates all of the above methods into an object where the Render property corresponds to genElement-related operations and staticRenderFns corresponds to string arrays.
export function generate (ast: ASTElement | void, options: CompilerOptions) :CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}} `.// render
staticRenderFns: state.staticRenderFns // Render function string array}}Copy the code
3, chestnut
After reading the above so much, some friends who are not familiar with VUE may feel a little dizzy. Here is a direct example of V-for rendering for everyone to understand.
i. demo
<div class="root">
<span v-for="n in 5" :key="n">{{ n }}</span>
</div>
Copy the code
This will first be parsed into an HTML string
let html = `
{{ n }}
`
Copy the code
Ii. Correlation regularization
Once the HTML string inside the template is retrieved, it is parsed. The specific regular expressions are mentioned in SRC/Compiler /parser/html-parser.js, and the following are some relevant regular expressions and the definition of decoding map.
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `
const startTagOpen = new RegExp(` ^ <${qnameCapture}`)
const startTagClose = /^\s*(\/?) >/
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `)
const doctype = / ^
]+>/i
const comment = / ^
const conditionalComment = / ^
const decodingMap = {
'< ': '<'.'> ': '>'.'" ': '"'.'& ': '&'.'& # 10; ': '\n'.'& # 9. ': '\t'
}
const encodedAttr = / & (? :lt|gt|quot|amp); /g
const encodedAttrWithNewLines = / & (? :lt|gt|quot|amp|#10|#9); /g
Copy the code
iii. parseHTML
Vue parses templates using a while loop for string matching. After parsing a string, it removes all matches and the index continues to match the rest of the string. The definition of parseHTML is as follows. Due to the length of this article, I have omitted some logic of the regular loop matching pointer. If you want to know more about parseHTML, you can study it by yourself or wait for my next article to discuss this logic in detail.
export function parseHTML (html, options) {
const stack = [] // Store the parsed tag header
const expectHTML = options.expectHTML
const isUnaryTag = options.isUnaryTag || no
const canBeLeftOpenTag = options.canBeLeftOpenTag || no
let index = 0 // Matches the pointer index
let last, lastTag
while (html) {
// Here is the logic for matching the tag
}
// Clear up the remaining tags
parseEndTag()
// Loop matching correlation processing
function advance (n) {
index += n
html = html.substring(n)
}
// Start tag related processing
function parseStartTag () {
let match = {
tagName: start[1].attrs: [].start: index
}
// a series of matching operations, and then an assignment to match
return match
}
function handleStartTag (match) {}
// End label processing
function parseEndTag (tagName, start, end) {}}Copy the code
After a series of regular matching processing by parseHTML(), the string HTML is parsed into the following AST contents
{
'attrsMap': {
'class': 'root'
},
'staticClass': 'root'.// The static class of the tag
'tag': 'div'.// 标签的 tag
'children': [{ // Sublabel array
'attrsMap': {
'v-for': "n in 5".'key': n
},
'key': n,
'alias': "n"./ / v - for parameters
'for': 5.// The object being v-for
'forProcessed': true.'tag': 'span'.'children': [{
'expression': '_s(item)'.// toString operation (mentioned above)
'text': '{{ n }}'}}}]]Copy the code
Generate is the logic behind render.
4. Diff and patch
Oh, it’s time for diff and Patch. It’s still freezing.
1. Some DOM API operations
Before looking for specific diff, let’s look at platforms/web/runtime/node – ops. Js defined in some of the ways to create real dom, just review the dom API related operations
createElement()
Created by thetagNameSpecifies the HTML element
export function createElement (tagName: string, vnode: VNode) :Element {
const elm = document.createElement(tagName)
if(tagName ! = ='select') {
return elm
}
if(vnode.data && vnode.data.attrs && vnode.data.attrs.multiple ! = =undefined) {
elm.setAttribute('multiple'.'multiple')}return elm
}
Copy the code
createTextNode()
Creating a text node
export function createTextNode (text: string) :Text {
return document.createTextNode(text)
}
Copy the code
createComment()
Create a comment node
export function createComment (text: string) :Comment {
return document.createComment(text)
}
Copy the code
insertBefore()
Inserts a child node with the specified parent node before the reference node
export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {
parentNode.insertBefore(newNode, referenceNode)
}
Copy the code
removeChild()
Removes a child node from the DOM
export function removeChild (node: Node, child: Node) {
node.removeChild(child)
}
Copy the code
appendChild()
Adds a node to the end of the list of children of the specified parent node
export function appendChild (node: Node, child: Node) {
node.appendChild(child)
}
Copy the code
parentNode()
Return to parent node
export function parentNode (node: Node): ?Node {
return node.parentNode
}
Copy the code
nextSibling()
Return sibling node
export function nextSibling (node: Node): ?Node {
return node.nextSibling
}
Copy the code
tagName()
Returns the node label name
export function tagName (node: Element) :string {
return node.tagName
}
Copy the code
setTextContent()
Set the text content of a node
export function setTextContent (node: Node, text: string) {
node.textContent = text
}
Copy the code
2. Some API operations in patch
Tip: The apis listed above hang in the nodeOps object below
createElm()
Create a node
function createElm (vnode, parentElm, refElm) {
if (isDef(vnode.tag)) { // Create a label node
vnode.elm = nodeOps.createElement(tag, vnode)
} else if (isDef(vnode.isComment)) { // Create a comment node
vnode.elm = nodeOps.createComment(vnode.text)
} else { // Create a text node
vnode.elm = nodeOps.createTextNode(vnode.text)
}
insert(parentElm, vnode.elm, refElm)
}
Copy the code
insert()
Specifies that a child node is inserted under the parent node
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) { // Insert before the specified ref
if (ref.parentNode === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else { // Insert directly after the parent node
nodeOps.appendChild(parent, elm)
}
}
}
Copy the code
addVnodes()
Batch callcreateElm()
To create a node
function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
createElm(vnodes[startIdx], parentElm, refElm)
}
}
Copy the code
removeNode()
Remove node
function removeNode (el) {
const parent = nodeOps.parentNode(el)
if (isDef(parent)) {
nodeOps.removeChild(parent, el)
}
}
Copy the code
removeNodes()
Removing Nodes in batches
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
removeNode(ch.elm)
}
}
}
Copy the code
sameVnode()
Whether the nodes are the same
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)
)
}
Copy the code
sameInputType()
Whether there is the same input type
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
}
Copy the code
3. Node diff
I. Relevant flow charts
Speaking of which, let’s use two related images from my previous article
Ii. Integration of DIff and Patch operations
Those who have read my previous articles should know that the implementation of DIff and patch in my previous articles is divided into two steps. In VUE, the diff and patch operations are combined. Now, how does vUE deal with this
function patch (oldVnode, vnode) {
// If the old node does not exist, create a new node
if (isUndef(oldVnode)) {
if (isDef(vnode)) createElm(vnode)
// If the old node exists but the new node does not, remove the old node
} else if (isUndef(vnode)) {
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
removeVnodes(parentElm, , 0, [oldVnode].length - 1)}else {
const isRealElement = isDef(oldVnode.nodeType)
// If the old and new nodes are the same, perform the patch operation
if (isRealElement && sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode)
} else {
// Otherwise, create a new node and remove the old node
createElm(vnode, parentElm, nodeOps.nextSibling(oldElm))
removeVnodes(parentElm, [oldVnode], 0.0)}}}Copy the code
Then we will look at the logic related to patchVnode. First, we will look at the use of the key mentioned above
function patchVnode (oldVnode, vnode) {
// If the old and new nodes are identical, return
if (oldVnode === vnode) {
return
}
// If both old and new nodes are labeled static nodes, and the nodes have the same key.
// Add componentInstance to componentInstance
if (
isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key
) {
vnode.componentInstance = oldVnode.componentInstance
return}}Copy the code
Next, how does the text content on vNode compare
- If vnode is a non-text node
const elm = vnode.elm = oldVnode.elm
const oldCh = oldVnode.children
const ch = vnode.children
if (isUndef(vnode.text)) {
// If both oldCh and ch exist and are different, the updateChildren function is executed to update the child node
if (isDef(oldCh) && isDef(ch)) {
if(oldCh ! == ch) updateChildren(elm, oldCh, ch)// If only ch exists
} else if (isDef(ch)) {
// The old node is a text node, delete the text of the old node first, and then insert ch into node ELM in batch
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
addVnodes(elm, null, ch, 0, ch.length - 1)
// If only oldCh exists, empty the old node
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
// If neither oldCh nor ch exists and the old node is a text node, only the text of the old node is cleared
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, ' ')}}Copy the code
- If the vnode is a text node and the text of the new and old nodes is different, the text content of the vnode is directly set to the text content of the vnode
if(isDef(vnode.text) && oldVnode.text ! == vnode.text) { nodeOps.setTextContent(elm, vnode.text) }Copy the code
iii. updateChildren
First let’s look at the definitions of the start and end indexes for the old and new nodes in the method
function updateChildren (parentElm, oldCh, newCh) {
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
}
Copy the code
So let me just draw a picture of it
This is followed by a while loop where the old and new nodes start and end the index closer to the middle
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
Copy the code
If oldStartVnode or oldEndVnode does not exist, move closer to the center
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
}
Copy the code
OldStartVnode, newStartVnode, oldEndVnode, newEndVnode
// oldStartVnode and newStartVnode are samevnodes. PatchVnode
// oldStartIdx and newStartIdx move back one bit
else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
// oldEndVnode and newEndVnode are samevnodes, patchVnode
// oldEndIdx and newEndIdx move forward one bit
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
// oldStartVnode and newEndVnode are samevnodes. PatchVnode
// Insert oldStartvNode. elm after oldendvNode. elm
// oldStartIdx moves back one bit, newEndIdx moves forward one bit
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode)
nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
// oldEndVnode and newStartVnode are samevnodes
// Insert oldEndvNode. elm before oldStartvNode. elm
// oldEndIdx moves forward one bit, newStartIdx moves back one bit
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode)
nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}
Copy the code
Use a diagram to summarize the above process
If none of the preceding conditions is met, perform other operations.
The createKeyToOldIdx function returns a map of the oldCh key and index table. The oldKeyTooldidx function returns a map of the oldCh key and index table. The oldKeyTooldidx function returns the oldCh key and index table
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
Copy the code
In addition, there is another helper function, findIdxInOld, that finds the index of newStartVnode in the oldCh array
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
Copy the code
Now let’s look at what happens when the above conditions are not met
else {
// If oldKeyToIdx does not exist, oldCh is converted to the map table corresponding to key and index
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
// If idxInOld does not exist, that is, no node with a key corresponding to newStartVnode exists on the old node, create a new node
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
// If the node corresponding to the key is found in oldCh and the node and newStartVnode are sameVnode, patchVnode is performed
// Empty the nodes at oldCh and insert vnodeToMove in front of oldStartVNode. elm in parentElm
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode)
oldCh[idxInOld] = undefined
nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// If the corresponding node is found but belongs to a different element, create a new node
createElm(newStartVnode, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
// newStartIdx moves back one bit
newStartVnode = newCh[++newStartIdx]
}
Copy the code
After a series of operations, the diff and patch operations between nodes are completed, that is, the conversion from oldVnode to newVnode is completed.
Here is the end of the article, see here, I believe that you have to vUE in the VDOM this also must have their own understanding. So, let’s go back to the question we posed at the beginning of this article. Do you know why this question arises?
Emmm, if you want to continue to communicate this problem, welcome you to join the group to discuss, front-end hodgepodge.731175396. Remember to join a group, even if you join a group together. (Note: there are a lot of single beautiful girls in the group, of course there are also a lot of handsome guys, such as… Me)
Personal ready to pick up their own public number, after the weekly guarantee of a high quality good article, interested partners can pay attention to a wave.