首页 > 技术文章 > .8-Vue源码之AST(4)

QH-Jimmy 2017-06-09 17:28 原文

  上一节讲完了超长的start函数,也同时完结了handleStartTag函数,接着continue进入下一轮while循环。

  此时剩余的字符串状态如图:,切掉了<div id='app'>。

  再次进入while循环时,发生了一些变化:

    // Line-7672
    function parseHTML(html, options) {
        /* code */

        while (html) {
            last = html;
            if (!lastTag || !isPlainTextElement(lastTag)) {
                var textEnd = html.indexOf('<');
                // 此时字符串不是以<开头 所以不会进入此条件
                if (textEnd === 0) {
                    // ...
                }
                var text = (void 0),
                    rest$1 = (void 0),
                    next = (void 0);
                if (textEnd >= 0) {
                    // 截取<字符索引 => </div>
                    rest$1 = html.slice(textEnd);
                    // 处理文本中的<字符
                    while (!endTag.test(rest$1) &&
                        !startTagOpen.test(rest$1) &&
                        !comment.test(rest$1) &&
                        !conditionalComment.test(rest$1)
                    ) {
                        next = rest$1.indexOf('<', 1);
                        if (next < 0) {
                            break
                        }
                        textEnd += next;
                        rest$1 = html.slice(textEnd);
                    }
                    // 获取中间的字符串 => {{message}}
                    text = html.substring(0, textEnd);
                    advance(textEnd);
                }
                // 当字符串没有<时
                if (textEnd < 0) {
                    text = html;
                    html = '';
                }
                // 另外一个函数
                if (options.chars && text) {
                    options.chars(text);
                }
            } else {
                /* code */
            }

            if (html === last) {
                /* code */
            }
        }
        // Clean up any remaining tags
        parseEndTag();

        // fn...
    }

  第一次进入while循环时,由于字符串以<开头,所以进入startTag条件,并进行AST转换,最后将对象弹入stack数组中。

  而这一次,字符串开头为{,所以会继续执行下面的代码。代码将{{message}}作为text抽离出来,并调用了参数中另外一个函数:options.chars。

    // Line-8167
    function chars(text) {
        if (!currentParent) {
            // 提示必须有一个DOM根节点
            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
        if (isIE &&
            currentParent.tag === 'textarea' &&
            currentParent.attrsMap.placeholder === text) {
            return
        }
        var children = currentParent.children;
        // text => {{message}}
        text = inPre || text.trim() ?
            isTextTag(currentParent) ? text : decodeHTMLCached(text) : preserveWhitespace && children.length ? ' ' : '';
        if (text) {
            var expression;
            if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
                // 将解析后的text弄进children数组
                children.push({
                    type: 2,
                    expression: expression,
                    text: text
                });
            } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
                children.push({
                    type: 3,
                    text: text
                });
            }
        }
    }

  本函数的核心为parseText对text的处理,即{{message}}。

    // Line-7928
    function parseText(text, delimiters) {
        // 正则选择
        var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE;
        // 在这里调用test方法后lasatIndex会变化
        if (!tagRE.test(text)) {
            return
        }
        var tokens = [];
        var lastIndex = tagRE.lastIndex = 0;
        var match, index;
        // 匹配到中间的文本
        while ((match = tagRE.exec(text))) {
            index = match.index;
            // 将{{message}}之前的文本push进去
            if (index > lastIndex) {
                tokens.push(JSON.stringify(text.slice(lastIndex, index)));
            }
            // 该方法对特殊字符进行处理 本例暂时用不上
            // 返回的仍然是message字符串
            var exp = parseFilters(match[1].trim());
            // _s(message)
            tokens.push(("_s(" + exp + ")"));
            lastIndex = index + match[0].length;
        }
        if (lastIndex < text.length) {
            // push}}后面的文本
            tokens.push(JSON.stringify(text.slice(lastIndex)));
        }
        return tokens.join('+')
    }

  实际上text可分为3个部分,{{之前的,{{}}中间包裹的,}}之后的,函数分别将三者抽离出来,push进了tokens,最后用+连接并返回一个字符串:

  返回后,将此字符串作为值,和其余属性一个添加到children数组中:

  

  处理完后,进入下一轮while循环。

  剩余的字符串为</div>,所以进入第一个循环,并且匹配到EndTag的分支。

    // Line-7672
    function parseHTML(html, options) {
        /* code */
        while (html) {
            /* code */
            var textEnd = html.indexOf('<');
            if (textEnd === 0) {
                /* code */
                var endTagMatch = html.match(endTag);
                if (endTagMatch) {
                    var curIndex = index;
                    advance(endTagMatch[0].length);
                    parseEndTag(endTagMatch[1], curIndex, index);
                    continue
                }
                /* code */
            }
            /* code */
        }
        /* code */
    }

  进入endTag分支后,匹配到的endTagMatch如图所示:

  将当前索引保存为curIndex,然后根据匹配到的字符串往前推index,调用parseEndTag函数进行处理。

    // Line-7863
    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) {
            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);
                }
            }

            // Remove the open elements from the stack
            stack.length = pos;
            // 0
            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);
            }
        }
    }

    // Line-8154
    function end() {
        // 获取对象与文本
        var element = stack[stack.length - 1];
        var lastNode = element.children[element.children.length - 1];
        // type是2 跳过
        if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
            element.children.pop();
        }
        // pop stack
        stack.length -= 1;
        // 变成undefined了
        currentParent = stack[stack.length - 1];
        endPre(element);
    }

    // Line-8010
    function endPre(element) {
        if (element.pre) {
            inVPre = false;
        }
        // tag === pre?
        if (platformIsPreTag(element.tag)) {
            inPre = false;
        }
    }

  这个函数对闭合标签进行配对,并对应将stack数组进行变动,由于本例只有一个div,所以stack被清空。

 

  完事后,continue进入下一轮循环,由于字符串全部被切割完,此时html为空字符串,此时while循环结束,进入下一个代码段:

    // Line-7672
    function parseHTML(html, options) {
        /* code */
        while (html) {
            /* code */
        }

        // Clean up any remaining tags
        parseEndTag();

        /* 一些方法 */
    }

  字符串解析完后,再次调用parseEndTag进行收尾工作,函数内部将pos置0,stack置空。

  回到了parse函数,并返回了root,即解析后的AST对象:,包含了标签类型、属性、文本内容等。

  

  先结束了吧。

                  

推荐阅读