This is the 17th day of my participation in the First Challenge 2022.
Previously on & Background
We are talking about the new Vue process, the last step of the this._init() method, $mount, which parses the template into an AST object and then converts the AST into a Render /staticRenderFns rendering function.
Parse calls parseHTML to parseHTML templates. Marked with <, it divides templates into comments, conditional comments, document declarations, start/close tags, and plain text types. Then call the comment/start/end/chars method in the options received by parseHTML.
ParseHTML (parseHTML) parseHTML (parseHTML) parseHTML (parseHTML)
export function parseHTML (html, options) {
// The code in this code is just a sketch of where the method is called
let index = 0
let last, lastTag
while (html) {
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)
advance(commentEnd + 3)
parseEndTag(endTagMatch[1], curIndex, index)
const startTagMatch = parseStartTag()
handleStartTag(startTagMatch)
options.chars(text, index - text.length, index)
parseEndTag(stackedTag, index - endTagLength, index)
options.chars && options.chars(html)
}
parseEndTag()
function advance (n) {}function parseStartTag () {}
function handleStartTag (match) {}
function parseEndTag (tagName, start, end) {}}Copy the code
Second, advance
Method location: within parseHTML
Method parameters: n, index The number of lengths to advance
Methods:
- in
parseHTML
Maintain theindex
Variables, summing up each timen
.index
It’s actually a pointer to the next location that needs to be processed, and this location is onehtml
Index of the template string; - maintenance
html
Variables,html
The variable starts out asparseHTML
Method receives the template string, along withwhile
Cyclic processingindex
And then gradually increase, andhtml
I need to change to the rest of the unprocessed string, the processhtml
Change to the substring of the original template, slowly shorten the length, and finally put allhtml
It’s all been treated;
function advance (n) {
index += n
html = html.substring(n)
}
Copy the code
Third, parseStartTag
Method location: within the parseHTML method
The
- call
html.match
Method, passed instartTagOpen
The re matches the start tag, as shown in the figure
-
Declare a match object containing the tagName, attrs, and start attributes. TagName is the name of the start tag, attrs is the inline attribute following the start tag, and start is the index value in parseHTML.
-
Call advance advance start[0]. Length, which contains div and is the first item returned by match
-
The while loop processes inline attributes after the start tag, such as id=”app”, and inserts the matching attribute into mate.attrs
-
The end of the start tag is matched during the while loop of 4. At the end of the match, the end attribute is added to the match object, advance is called to advance the end length, and the match ‘object is returned
function parseStartTag () {
const start = html.match(startTagOpen)
if (start) {
const match = {
tagName: start[1].attrs: [].start: index
}
advance(start[0].length)
let end, attr
// A while loop terminates by matching the end of the end start tag; No match was found for a dynamic parameter attribute or a normal attribute
// Start the attributes in the tag and place them in the match-.attrs array
while(! (end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) { attr.start = index advance(attr[0].length)
attr.end = index
match.attrs.push(attr)
}
// The corresponding end of the start tag, such as
or /> for the closure and tag.
if (end) {
match.unarySlash = end[1]
advance(end[0].length)
match.end = index
return match
}
}
}
Copy the code
The match object looks like this:
To sum up:
In the while loop, index is used as a pointer. After matching comments, conditional comments, start tags, etc., corresponding methods are called for processing. After processing, index will be maintained incrementing and HTML maintenance will be gradually shortened. You can see a little bit of this in the parseStartTag method, which starts parsing from the start tag, extracts useful information from the HTML string, and then shorts the HTML by advancing the index (which is what advance does). Then match the > or /> of the autism and match to the end, and the start tag is closed. Index advances the length of the string
The handleStartTag method
Method location: inside the parseHTML method
Method argument: match, the match object returned by parseStartTag in the previous step
Methods:
- Parse the previous step
parseStartTag
The returnedmatch
Object, processingmatch.attrs
,match
In theatts
like['id="app"', 'id', '=', 'app', undefined, undefined]
become{ name: 'id', value: 'app' }
This form; - If it’s not autism and tag, put the tag information in
stack
In order to pop up when its closing tag is processed in the futurestack
To accomplish thishtml
Label pairing problem processing. This approach is similar to a classic interview question with multiple brackets closed, but it’s another way to use itwhile
This one-dimensional loopHTML
The key point of this tree structure; And autism and labels, that isUnary unary tag
, directly processing the information on the label;push
到stack
The object is as follows. WaitparseEnd
You need to use it
stack.push({
tag: tagName,
lowerCasedTag: tagName.toLowerCase(),
attrs: attrs,
start: match.start,
end: match.end
})
Copy the code
- Next call
options.start
Method, passed inTagName, attrs, mate. start, mate. end
function handleStartTag (match) {
const tagName = match.tagName // Label name
const unarySlash = match.unarySlash // Whether there are autistic and slashes, that is, / in />
if (expectHTML) {
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag)
}
if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
parseEndTag(tagName)
}
}
// Unary tags, i.e., autistic tags and tags, such as \
constunary = isUnaryTag(tagName) || !! unarySlashAttrs [I] = [{name: attrName, value: attrVal, star: xx, end: xx},...]
// Unprocessed attrs looks like this:
// attrs = [['id="app"', 'id', '=', 'app', undefined, undefined], ...]
const l = match.attrs.length
const attrs = new Array(l)
for (let i = 0; i < l; i++) {
const args = match.attrs[i]
// arg[3] = 'app', args[4]= undefined, args[5]=undefined
const value = args[3] || args[4] || args[5] | |' '
const shouldDecodeNewlines = tagName === 'a' && args[1= = ='href'
? options.shouldDecodeNewlinesForHref // If it is an A tag, the href value is encoded
: options.shouldDecodeNewlines
Atrrs [I] {name, value}
attrs[i] = {
name: args[1].value: decodeAttr(value, shouldDecodeNewlines)
}
// Record the start and end indexes of attributes in non-production environments
if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
attrs[i].start = args.start + args[0].match(/^\s*/).length
attrs[i].end = args.end
}
}
// If it is not closed and tag, put the tag information into the stack array and exit the stack when the end tag is processed
if(! unary) {{tag, lowerCasedTag, attrs, start, end}
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
lastTag = tagName // Save the current label name to lastTag
}
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end)
}
}
Copy the code
Fifth, parseEndTag
Method location: parseHTML internal method
Method parameters:
tagnName
: Name of a closed labelstart
: Start indexend
: End index
Methods:
-
Define POS traversal. If the tagName of the closed tag is passed, it will search backwards from the stack until it finds the item in the stack whose tagName attribute is the same as that of the passed tagName attribute. Its index is the value of POS. The item at the top of the stack is the opening tag data corresponding to the received closing tag tagName;
-
When dealing with a stack, when there are no unclosed elements, the top of the stack is the corresponding opening label. At this point, find the corresponding item in the stack of the closing tag and let it go off the stack, always keeping the last item in the stack is the information corresponding to the start tag when the HTML template matches the end tag;
function parseEndTag (tagName, start, end) {
let pos, lowerCasedTagName
if (start == null) start = index
if (end == null) end = index
if (tagName) {
lowerCasedTagName = tagName.toLowerCase()
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
break}}}else {
// If no tag name is provided, clean shop
pos = 0
}
// If the closing tag is not found in the for sequence of the start tag, the closing tag is not found in the for sequence of the start tag.
// Pos exits the loop because it keeps decreasing to less than 0,
// Pos < 0, indicating that the start tag is not found, and there is an open tag
if (pos >= 0) {
// The corresponding closed label is found
for (let i = stack.length - 1; i >= pos; i--) {
if(process.env.NODE_ENV ! = ='production'&& (i > pos || ! tagName) && options.warn ) {// In principle, the top of the stack is the closed element of the current tagName
// If there are elements before pos, these elements also have no closing tags
options.warn(
`tag <${stack[i].tag}> has no matching end tag.`,
{ start: stack[i].start, end: stack[i].end }
)
}
if (options.end) {
// Call the options.end method to process the closing tag
options.end(stack[i].tag, start, end)
}
}
// Pos = stack. Length-1;
// Delete an item by modifying the array length attribute
stack.length = pos
// Reassign lastTag and record the name of a start tag to be processed in the stack array
// It is the name of the next closed tag
lastTag = pos && stack[pos - 1].tag
} else if (lowerCasedTagName === 'br') {
// Process the
tag scenario
if (options.start) {
options.start(tagName, [], true, start, end)
}
} else if (lowerCasedTagName === 'p') {
// p label status
if (options.start) {
// Process the tag
options.start(tagName, [], false, start, end)
}
if (options.end) {
// Process the tag
options.end(tagName, start, end)
}
}
}
Copy the code
Six, summarized
This essay describes the tools used in the process of parseHTML:
advance
: maintenanceindex
和html
.index
Recorded in the originalhtml
Processing position in,html
Gradually shorten into untreated parts;parseStartTag
: Matches the start tag with the retagName
和attrs
And the end of the start tag>
or/>
, and pushedstack
Information about the current start tag;handleStartTag
: Further processingparsetStartTag
To get thematch
Object, transformattrs
The array;parseEndTag
: Maintains when the end tag is matchedstack
To makestack
The start tag corresponding to the current end tag is out of the stack;