Vue源碼解讀之AST語法樹(二)

parseHTML()執行完第一次循環以後,html變成:javascript

clipboard.png
接下來開始執行第二次循環,解析的是文本"(回車)請輸入:"html

while (html) {
    last = html;
    if (!lastTag || !isPlainTextElement(lastTag)) {
      var textEnd = html.indexOf('<');
      if (textEnd === 0) {
         /*當新的html以<開頭,執行如下代碼*/
      }
      //新的循環執行這段
      var text = (void 0), rest = (void 0), next = (void 0);
      if (textEnd >= 0) {
        //截取字符串,存放在rest中
        rest = html.slice(textEnd);
        while (
          !endTag.test(rest) &&
          !startTagOpen.test(rest) &&
          !comment.test(rest) &&
          !conditionalComment.test(rest)
        ) {
          // 若是純文本中存在<,做爲文本處理
          next = rest.indexOf('<', 1);
          if (next < 0) { break }
          textEnd += next;
          rest = html.slice(textEnd);
        }
        //獲取文本字符串,此處就是‘(回車)請輸入:’
        text = html.substring(0, textEnd);
        //從新修改index索引,從新截取html
        advance(textEnd);
      }

      if (textEnd < 0) {
        text = html;
        html = '';
      }

      if (options.chars && text) {
        //調用parseHTML裏面定義的chars方法
        options.chars(text);
      }
    } else {
      /*代碼已省略*/
    }
    //當html是最後一段時間運行這段代碼
    if (html === last) {
      /*代碼已省略*/
    }
  }

第二次循環,字符串爲純文本"(回車)請輸入:",因此會繼續執行下面的代碼,調用parseHTML裏面定義的chars方法。java

chars

chars: function chars (text) {
      if (!currentParent) {
        {
          if (text === template) {
            warnOnce(
              'Component template requires a root element, rather than just text.'
            );
          } else if ((text = text.trim())) {
            warnOnce(
              ("text \"" + text + "\" outside root element will be ignored.")
            );
          }
        }
        return
      }
      // 處理IE下textarea placeholder的 bug,學習於https://blog.csdn.net/wide288/article/details/51094041
      if (isIE &&
        currentParent.tag === 'textarea' &&
        currentParent.attrsMap.placeholder === text
      ) {
        return
      }
      var children = currentParent.children;
      text = inPre || text.trim()
        ? isTextTag(currentParent) ? text : decodeHTMLCached(text)
        // only preserve whitespace if its not right after a starting tag
        : preserveWhitespace && children.length ? ' ' : '';
      if (text) {
        var res;
        if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
          children.push({
            type: 2,
            expression: res.expression,
            tokens: res.tokens,
            text: text
          });
        } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
        //將解析後的text push到children數組
          children.push({
            type: 3,
            text: text
          });
        }
      }
    }

chars方法對text進行處理,並轉換成children添加到當天父節點下。這裏text是純靜態文本,走的代碼是children.push({type: 3,text: text});,將type定爲3,若text是{{message}},則走的是children.push({type: 2,expression: res.expression, tokens: res.tokens,text: text});,將type設爲2。
接下來執行第三次循環,解析<input type="text" v-model="message">express

clipboard.png
解析過程與第一次相同。
第四次循環解析<br/>數組

clipboard.png
解析過程與第一次相同。
第五次循環解析的時<的位置是1,所以解析的實際上是回車,,text就是"",解析過程與第二次相同。但最後在調用end方法是,該條children會從數組中pop出來。ide

clipboard.png
第六次循環解析的就是結束標籤</div>學習

clipboard.png
在parseHTML()匹配到endTag以後調用parseEndTag()方法。ui

parseEndTag

function parseEndTag (tagName, start, end) {
    var pos, lowerCasedTagName;
    if (start == null) { start = index; }
    if (end == null) { end = index; }

    //標籤名轉成小寫
    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase();
    }

    // 獲取與結束標籤匹配的最近的標籤
    if (tagName) {
      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 (pos >= 0) {
      // Close all the open elements, up the stack
      for (var i = stack.length - 1; i >= pos; i--) {
        //// 提示沒有匹配的標籤
        if ("development" !== 'production' &&
          (i > pos || !tagName) &&
          options.warn
        ) {
          options.warn(
            ("tag <" + (stack[i].tag) + "> has no matching end tag.")
          );
        }
        if (options.end) {
          options.end(stack[i].tag, start, end);
        }
      }

      //對應將stack數組進行變更
      //pos=0;
      stack.length = pos;
      lastTag = pos && stack[pos - 1].tag;
    } else if (lowerCasedTagName === 'br') {
      if (options.start) {
        options.start(tagName, [], true, start, end);
      }
    } else if (lowerCasedTagName === 'p') {
      if (options.start) {
        options.start(tagName, [], false, start, end);
      }
      if (options.end) {
        options.end(tagName, start, end);
      }
    }
  }

parseEndTag()首先對閉合標籤進行匹配,將start和end的值設爲index最後的索引值,即html的末位,而後調用options裏面的end方法清空stack,最後修改stack的長度爲0。end方法的代碼以下:spa

end

end: function end () {
      // 獲取對象與文本
      var element = stack[stack.length - 1];
      var lastNode = element.children[element.children.length - 1];
      if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
        //若是lastNode是空文本,把lastNode彈出
        element.children.pop();
      }
      // pop stack
      stack.length -= 1;
      currentParent = stack[stack.length - 1];
      closeElement(element);
    }

至此parseHTML循環結束,返回的ast對象有以下這些信息:.net

clipboard.png

相關文章
相關標籤/搜索