博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.7-Vue源码之AST(3)
阅读量:5172 次
发布时间:2019-06-13

本文共 14803 字,大约阅读时间需要 49 分钟。

  上一节到了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                        }                    }                    // 处理向下兼容的注释 比如说

  函数比较长,除去开头的参数获取,后面直接用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) {                /*
=>
*/ } } // 将原始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) { /*
=>
*/ } } 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}。

  这函数有点长。

  

 

转载于:https://www.cnblogs.com/QH-Jimmy/p/6951643.html

你可能感兴趣的文章
面试题之表单验证
查看>>
找到多个与名为“Login”的控制器匹配的类型
查看>>
myeclipse使用步骤总结
查看>>
贝壳:月光宝盒的密码(二分查找,暴力破解,动态规划)
查看>>
KVM虚拟机内存不足,调整参数
查看>>
POJ 1486 Sorting Slides (二分图关键匹配边)
查看>>
5月2日下午学习日志
查看>>
js数组
查看>>
木其工作室(专业程序代写服务)[转]Linux设备驱动程序学习-中断处理
查看>>
[TWRP 2.8.4 ] 小米 3W 中文-英文版本 twrp
查看>>
(最短路)17bupt新生赛——F. ch追妹
查看>>
mysql5.6免安装版配置
查看>>
node.js express安装问题
查看>>
文本处理 - 测试一个对象是否是类字符串
查看>>
如何使用shell收集linux系统状态,并把结果发给远端服务器
查看>>
【转载】Xpath定位方法深入探讨及元素定位失败常见情况
查看>>
eclipse中如何远程java debug配置
查看>>
Tomcat7安装和配置以及优化
查看>>
Docker 架构(二)【转】
查看>>
jQuery Tips(5)----关于伪类选择符
查看>>