• .7-Vue源码之AST(3)


      上一节到了parseHTML函数,该函数接受一个字符串与一个对象,字符串即对应的DOM,对象包含几个字符串匹配集及3个长函数。

      简略梳理部分函数代码如下:

        // Line-7672
        function parseHTML(html, options) {
            var stack = [];
            var expectHTML = options.expectHTML;
            var isUnaryTag$$1 = options.isUnaryTag || no;
            var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
            var index = 0;
            var last, lastTag;
            while (html) {
                last = html;
                // 排除script,style,textarea三个标签
                if (!lastTag || !isPlainTextElement(lastTag)) {
                    var textEnd = html.indexOf('<');
                    if (textEnd === 0) {
                        // 截取注释
                        if (comment.test(html)) {
                            var commentEnd = html.indexOf('-->');
    
                            if (commentEnd >= 0) {
                                advance(commentEnd + 3);
                                continue
                            }
                        }
                        // 处理向下兼容的注释 比如说<!--[if lt IE 9]>
                        if (conditionalComment.test(html)) {
                            var conditionalEnd = html.indexOf(']>');
    
                            if (conditionalEnd >= 0) {
                                advance(conditionalEnd + 2);
                                continue
                            }
                        }
                        // Doctype:
                        var doctypeMatch = html.match(doctype);
                        if (doctypeMatch) {
                            advance(doctypeMatch[0].length);
                            continue
                        }
                        // End tag:
                        var endTagMatch = html.match(endTag);
                        if (endTagMatch) {
                            var curIndex = index;
                            advance(endTagMatch[0].length);
                            parseEndTag(endTagMatch[1], curIndex, index);
                            continue
                        }
                        // Start tag:
                        // 匹配起始标签
                        var startTagMatch = parseStartTag();
                        if (startTagMatch) {
                            handleStartTag(startTagMatch);
                            continue
                        }
                    }
                    // 初始化为undefined 这样安全且字符数少一点
                    var text = (void 0),
                        rest$1 = (void 0),
                        next = (void 0);
                    if (textEnd >= 0) {
                        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);
                        }
                        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();
    
            function advance(n) {
                /* code... */
            }
    
            function parseStartTag() {
                /* code... */
            }
    
            function handleStartTag(match) {
                /* code... */
            }
    
            function parseEndTag(tagName, start, end) {
                /* code... */
            }
        }

      函数比较长,除去开头的参数获取,后面直接用while循环开始对字符串进行切割。

      在判断标签不是script,style,textarea三个特殊标签后,当字符串以<开头时,以注释、向下兼容注释、Doctype、结束标签、起始标签的顺序依次切割。

      由于案例中字符串是以<div开头,所以直接跳到起始标签的匹配:

        // Line-7722
        var startTagMatch = parseStartTag();
        if (startTagMatch) {
            handleStartTag(startTagMatch);
            continue
        }
    
        // Line-7795
        function parseStartTag() {
            // 正则匹配
            var start = html.match(startTagOpen);
            if (start) {
                var match = {
                    // 标签名(div)
                    tagName: start[1],
                    // 属性
                    attrs: [],
                    // 游标索引(初始为0)
                    start: index
                };
                advance(start[0].length);
                var end, attr;
                // 进行属性的正则匹配
                // startTagClose匹配/>或>
                // attribute匹配属性 正则太长 没法讲
                // 本例中attr匹配后 => ['id=app','id','=','app']
                while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                    advance(attr[0].length);
                    // 属性加入
                    match.attrs.push(attr);
                }
                // 在第二次while循环后 end匹配到结束标签 => ['>','']
                if (end) {
                    match.unarySlash = end[1];
                    advance(end[0].length);
                    // 标记结束位置
                    match.end = index;
                    // 返回匹配对象
                    return match
                }
            }
        }
    
        // Line-7790
        // 该函数将函数局部变量index往前推 并切割字符串
        function advance(n) {
            index += n;
            html = html.substring(n);
        }

      可以看到,通过起始标签的匹配,字符串的<div id='app'>已经被切割出来,保存在一个对象中返回:

      接下来,会调用handleStartTag方法再次处理返回的对象,看一下这个方法:

        // Line-7818
        function handleStartTag(match) {
            var tagName = match.tagName;
            var unarySlash = match.unarySlash;
    
            if (expectHTML) {
                // PhrasingTag(段落元素)涉及到标签元素类型 具体可见http://www.5icool.org/a/201308/a2081.html
                if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
                    parseEndTag(lastTag);
                }
                // 可以省略闭合标签
                if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
                    parseEndTag(tagName);
                }
            }
    
            // 自闭合标签
            var unary = isUnaryTag$$1(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
    
            // 记录属性个数 目前只有一个id属性
            var l = match.attrs.length;
            var attrs = new Array(l);
            for (var i = 0; i < l; i++) {
                var args = match.attrs[i];
                // 一个bug 在对(.)?匹配时会出现
                if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
                    if (args[3] === '') {
                        delete args[3];
                    }
                    if (args[4] === '') {
                        delete args[4];
                    }
                    if (args[5] === '') {
                        delete args[5];
                    }
                }
                // 匹配属性名app
                var value = args[3] || args[4] || args[5] || '';
                attrs[i] = {
                    name: args[1],
                    // 处理转义字符
                    value: decodeAttr(
                        value,
                        options.shouldDecodeNewlines
                    )
                };
            }
            // 将切割出来的字符串转换为AST
            if (!unary) {
                stack.push({
                    tag: tagName,
                    lowerCasedTag: tagName.toLowerCase(),
                    attrs: attrs
                });
                // 标记结束标签
                lastTag = tagName;
            }
    
            // 这是参数中第一个函数
            if (options.start) {
                options.start(tagName, attrs, unary, match.start, match.end);
            }
        }
    
        // Line-7667
        function decodeAttr(value, shouldDecodeNewlines) {
            // lg,gt等字符的正则
            var re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr;
            return value.replace(re, function(match) {
                return decodingMap[match];
            })
        }

      在该函数中,对之前的对象进行了二次处理,根据标签名、属性生成一个新对象,push到最开始的stack数组中,结果如图所示:

      由于匹配的是起始标签,所以也会以这个标签名结束,因此被标记为最后的结束标签,即前面一直是undefined的lastTag。

     

      最后,调用了一个start函数,蒙了半天没找到,后来才发现是最开始传进来的参数中有3个函数:start、end、chars,现在可以看一下这个方法干啥用的了。

      start方法只接受3个参数,这里传了5个,后面2个被忽略,参数情况是这样的:

      这个方法比较长:

        // Line-8026
        function start(tag, attrs, unary) {
            // 检查命名空间是否是svg或者math
            var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
    
            // handle IE svg bug
            if (isIE && ns === 'svg') {
                attrs = guardIESVGBug(attrs);
            }
    
            var element = {
                type: 1,
                tag: tag,
                // {name:'id',value:'app'}
                attrsList: attrs,
                // {id:'app'}
                attrsMap: makeAttrsMap(attrs),
                parent: currentParent,
                children: []
            };
            if (ns) {
                element.ns = ns;
            }
            // 检查tag属性是否是style、script
            if (isForbiddenTag(element) && !isServerRendering()) {
                element.forbidden = true;
                /* warning */
            }
    
            // apply pre-transforms 本例中没有
            // Line-7990:preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
            for (var i = 0; i < preTransforms.length; i++) {
                preTransforms[i](element, options);
            }
    
            if (!inVPre) {
                // 判断是否有v-pre属性
                processPre(element);
                if (element.pre) {
                    inVPre = true;
                }
            }
            // 判断tag是不是pre
            if (platformIsPreTag(element.tag)) {
                inPre = true;
            }
            // 分支跳转到else
            if (inVPre) {
                processRawAttrs(element);
            } else {
                // 处理v-for
                processFor(element);
                // 处理v-if,v-else,v-else-if
                processIf(element);
                // 处理v-once
                processOnce(element);
                // 处理:
                processKey(element);
    
                // 检测是否是空属性节点
                element.plain = !element.key && !attrs.length;
    
                // 处理:ref或v-bind:ref属性
                processRef(element);
                // 当tag为slot时
                processSlot(element);
                // 处理:is或v-bind:is属性
                processComponent(element);
                // Line-7991:transforms = pluckModuleFunction(options.modules, 'transformNode');
                // 处理class与style属性 包括原始的和通过:动态绑定
                for (var i$1 = 0; i$1 < transforms.length; i$1++) {
                    transforms[i$1](element, options);
                }
                // 处理属性
                processAttrs(element);
            }
    
            // 根元素不允许为slot或template 且不能有v-for属性
            // 总之必须为单一不可变的节点
            function checkRootConstraints(el) {
                {
                    if (el.tag === 'slot' || el.tag === 'template') {
                        warnOnce(
                            "Cannot use <" + (el.tag) + "> as component root element because it may " +
                            'contain multiple nodes.'
                        );
                    }
                    if (el.attrsMap.hasOwnProperty('v-for')) {
                        warnOnce(
                            'Cannot use v-for on stateful component root element because ' +
                            'it renders multiple elements.'
                        );
                    }
                }
            }
    
            // tree management
            // 这个root是在parse函数开始的时候定义的
            if (!root) {
                root = element;
                checkRootConstraints(root);
            } else if (!stack.length) {
                // allow root elements with v-if, v-else-if and v-else
                if (root.if && (element.elseif || element.else)) {
                    checkRootConstraints(element);
                    addIfCondition(root, {
                        exp: element.elseif,
                        block: element
                    });
                } else {
                    warnOnce(
                        "Component template should contain exactly one root element. " +
                        "If you are using v-if on multiple elements, " +
                        "use v-else-if to chain them instead."
                    );
                }
            }
            // 没有父元素 跳过
            if (currentParent && !element.forbidden) {
                if (element.elseif || element.else) {
                    processIfConditions(element, currentParent);
                } else if (element.slotScope) { // scoped slot
                    currentParent.plain = false;
                    var name = element.slotTarget || '"default"';
                    (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
                } else {
                    currentParent.children.push(element);
                    element.parent = currentParent;
                }
            }
            if (!unary) {
                currentParent = element;
                stack.push(element);
            } else {
                endPre(element);
            }
            // 没有这个 跳过
            // Line-7992:postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
            for (var i$2 = 0; i$2 < postTransforms.length; i$2++) {
                postTransforms[i$2](element, options);
            }
        }
    
        // Line-8470
        // 函数作用:attrs={name:'id',value:'app'} => map = {id : app}
        function makeAttrsMap(attrs) {
            var map = {};
            for (var i = 0, l = attrs.length; i < l; i++) {
                // 检测重复属性名
                if (
                    "development" !== 'production' &&
                    map[attrs[i].name] && !isIE && !isEdge
                ) {
                    warn$2('duplicate attribute: ' + attrs[i].name);
                }
                map[attrs[i].name] = attrs[i].value;
            }
            return map
        }

      这个方法首先对标签名进行校验,然后再对属性进行更细致的处理,比如说v-pre,v-for,v-if等等,案例里没有,所以暂时不分析如何处理,下次再搞。

      值得注意的是transforms这个数组,包含两个函数:transformNode与transformNode$1,其实只是对静态或动态绑定的class与style进行处理,代码如下:

        // Line-9416
        function transformNode(el, options) {
            var warn = options.warn || baseWarn;
            // 获取原始class属性
            var staticClass = getAndRemoveAttr(el, 'class');
            if ("development" !== 'production' && staticClass) {
                var expression = parseText(staticClass, options.delimiters);
                if (expression) {
                    /*<div class="{{ val }}"> => <div :class="val"> */
                }
            }
            // 将原始class属性保存为属性
            if (staticClass) {
                el.staticClass = JSON.stringify(staticClass);
            }
            // 获取:class 
            var classBinding = getBindingAttr(el, 'class', false /* getStatic */ );
            if (classBinding) {
                el.classBinding = classBinding;
            }
        }
    
        // Line-9458
        function transformNode$1(el, options) {
            var warn = options.warn || baseWarn;
            var staticStyle = getAndRemoveAttr(el, 'style');
            if (staticStyle) {
                /* istanbul ignore if */
                {
                    var expression = parseText(staticStyle, options.delimiters);
                    if (expression) {
                        /*<div style="{{ val }}"> => <div :style="val"> */
                    }
                }
                el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
            }
    
            var styleBinding = getBindingAttr(el, 'style', false /* getStatic */ );
            if (styleBinding) {
                el.styleBinding = styleBinding;
            }
        }

      在最后,调用processAttrs对动态绑定的属性(v-,@,:)进行处理,代码如下:

        // Line-8376
        function processAttrs(el) {
            // {name:'id',value:'app'}
            var list = el.attrsList;
            var i, l, name, rawName, value, modifiers, isProp;
            for (i = 0, l = list.length; i < l; i++) {
                // id
                name = rawName = list[i].name;
                // app
                value = list[i].value;
                // dirRE => 以v-、@、:开头
                if (dirRE.test(name)) {
                    // 标记为拥有动态绑定属性 本例中没有 跳过……
                    el.hasBindings = true;
                    // modifiers
                    modifiers = parseModifiers(name);
                    if (modifiers) {
                        name = name.replace(modifierRE, '');
                    }
                    if (bindRE.test(name)) { // v-bind
                        name = name.replace(bindRE, '');
                        value = parseFilters(value);
                        isProp = false;
                        if (modifiers) {
                            if (modifiers.prop) {
                                isProp = true;
                                name = camelize(name);
                                if (name === 'innerHtml') {
                                    name = 'innerHTML';
                                }
                            }
                            if (modifiers.camel) {
                                name = camelize(name);
                            }
                            if (modifiers.sync) {
                                addHandler(
                                    el,
                                    ("update:" + (camelize(name))),
                                    genAssignmentCode(value, "$event")
                                );
                            }
                        }
                        if (isProp || platformMustUseProp(el.tag, el.attrsMap.type, name)) {
                            addProp(el, name, value);
                        } else {
                            addAttr(el, name, value);
                        }
                    } else if (onRE.test(name)) { // v-on
                        name = name.replace(onRE, '');
                        addHandler(el, name, value, modifiers, false, warn$2);
                    } else { // normal directives
                        name = name.replace(dirRE, '');
                        // parse arg
                        var argMatch = name.match(argRE);
                        var arg = argMatch && argMatch[1];
                        if (arg) {
                            name = name.slice(0, -(arg.length + 1));
                        }
                        addDirective(el, name, rawName, value, arg, modifiers);
                        if ("development" !== 'production' && name === 'model') {
                            checkForAliasModel(el, value);
                        }
                    }
                } else {
                    {
                        var expression = parseText(value, delimiters);
                        if (expression) {
                            /* warn */
                        }
                    }
                    // 添加了个attrs属性
                    addAttr(el, name, JSON.stringify(value));
                }
            }
        }

      到此,element的属性如图所示:,attrs和attrsList是一样的,都是一个数组,包含一个对象,值为{name:id,value:app}。

      这函数有点长。

      

  • 相关阅读:
    C# 判断中文字符的8种方法
    C# GridView页脚汇总
    .NET开发人员十大必备下载工具
    参数修饰符ref,out ,params的区别
    C#一行代码登陆QQ居然碰到这么多麻烦(有意思)
    IIS5IIS6IIS7的ASP.net 请求处理过程比较
    以下放在作业里做调度,每天自动备份和自动删除三天前的备份
    ASP.NET行变色,及禁用编辑,删除按钮
    按钮点击连续触发
    Excel文件的读写实现
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/6951643.html
Copyright © 2020-2023  润新知