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:

  1. inparseHTMLMaintain theindexVariables, summing up each timen.indexIt’s actually a pointer to the next location that needs to be processed, and this location is onehtmlIndex of the template string;
  2. maintenancehtmlVariables,htmlThe variable starts out asparseHTMLMethod receives the template string, along withwhileCyclic processingindexAnd then gradually increase, andhtmlI need to change to the rest of the unprocessed string, the processhtmlChange to the substring of the original template, slowly shorten the length, and finally put allhtmlIt’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

method is used to parse the start tag, using the

as an example, to get a match object that describes the start tag. The match object is an object containing the tagName described by the tagName attribute, the set of inline attributes described by the start tag attribute, and so on. It is mainly divided into the following steps:

  1. callhtml.matchMethod, passed instartTagOpenThe re matches the start tag, as shown in the figure

  1. 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.

  2. Call advance advance start[0]. Length, which contains div and is the first item returned by match

  3. The while loop processes inline attributes after the start tag, such as id=”app”, and inserts the matching attribute into mate.attrs

  4. 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

, and HTML no longer contains 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:

  1. Parse the previous stepparseStartTagThe returnedmatchObject, processingmatch.attrs,matchIn theattslike['id="app"', 'id', '=', 'app', undefined, undefined]become{ name: 'id', value: 'app' }This form;
  2. If it’s not autism and tag, put the tag information instackIn order to pop up when its closing tag is processed in the futurestackTo accomplish thishtmlLabel pairing problem processing. This approach is similar to a classic interview question with multiple brackets closed, but it’s another way to use itwhileThis one-dimensional loopHTMLThe key point of this tree structure; And autism and labels, that isUnary unary tag, directly processing the information on the label;pushstackThe object is as follows. WaitparseEndYou need to use it
stack.push({ 
  tag: tagName, 
  lowerCasedTag: tagName.toLowerCase(), 
  attrs: attrs, 
  start: match.start,
  end: match.end 
})
Copy the code
  1. Next calloptions.startMethod, 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:

  1. tagnName: Name of a closed label
  2. start: Start index
  3. end: End index

Methods:

  1. 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;

  2. 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:

  1. advance: maintenanceindexhtml.indexRecorded in the originalhtmlProcessing position in,htmlGradually shorten into untreated parts;
  2. parseStartTag: Matches the start tag with the retagNameattrsAnd the end of the start tag>or/>, and pushedstackInformation about the current start tag;
  3. handleStartTag: Further processingparsetStartTagTo get thematchObject, transformattrsThe array;
  4. parseEndTag: Maintains when the end tag is matchedstackTo makestackThe start tag corresponding to the current end tag is out of the stack;