.9-Vue源码之AST(5)

  上节跑完了超长的parse函数:

    // Line-9261
    function baseCompile(template, options) {
        // Done!
        var ast = parse(template.trim(), options);
        // go!
        optimize(ast, options);
        var code = generate(ast, options);
        return {
            ast: ast,
            render: code.render,
            staticRenderFns: code.staticRenderFns
        }
    }

  返回一个ast对象,包括attrs、attrsList、attrsMap、children、plain、tag、type等属性,如图:

  包含了DOM字符串中节点类型、属性、文本内容,接下来进入优化函数:optimize。

    // Line-8648
    function optimize(root, options) {
        if (!root) {
            return
        }
        // 缓存静态标签
        isStaticKey = genStaticKeysCached(options.staticKeys || '');
        isPlatformReservedTag = options.isReservedTag || no;
        // 标记非静态节点
        markStatic$1(root);
        // 标记静态节点
        markStaticRoots(root, false);
    }

  首先函数会对静态属性进行缓存:

    // Line-8558
    function genStaticKeys$1(keys) {
        return makeMap(
            'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
            (keys ? ',' + keys : '')
        )
    }

  可以看到,上面的好像全是静态的属性。

  

  接下来调用markStatic$1标记非静态节点:

    // Line-8648
    function markStatic$1(node) {
        // 判断是否是静态节点
        node.static = isStatic(node);
        if (node.type === 1) {
            // 静态节点不包括slot和template标签
            if (!isPlatformReservedTag(node.tag) &&
                node.tag !== 'slot' &&
                node.attrsMap['inline-template'] == null
            ) {
                return
            }
            for (var i = 0, l = node.children.length; i < l; i++) {
                var child = node.children[i];
                // 递归处理子节点
                markStatic$1(child);
                // 子节点为false 父节点也是false
                if (!child.static) {
                    node.static = false;
                }
            }
        }
    }

  函数对节点做了三重判断,首先用isStatic方法判断类型。

    // Line-8621
    function isStatic(node) {
        if (node.type === 2) { // expression
            return false
        }
        if (node.type === 3) { // text
            return true
        }
        return !!(node.pre || (!node.hasBindings && // no dynamic bindings
            !node.if && !node.for && // not v-if or v-for or v-else
            !isBuiltInTag(node.tag) && // not a built-in
            isPlatformReservedTag(node.tag) && // not a component
            !isDirectChildOfTemplateFor(node) && // 判断是否v-for的子节点
            Object.keys(node).every(isStaticKey) // 遍历判断属性是否静态
        ))
    }

  排除type为2的表达式和3的文本,然后对属性进行遍历,若存在v-的动态属性,则会出现对应的属性,注释已经写出来了,这里就不做说明了。

  由于本案例只有一个动态文本,所以这里返回的是true。

 

  接着判断标签是否是slot或者template,此类动态标签不属性静态节点。

  最后对子节点,即children属性中的内容进行递归处理。

  函数完成后,ast对象会多一个属性,即static:false。

 

  剩下一个是对静态节点进行标记:

    // Line-8587
    function markStaticRoots(node, isInFor) {
        if (node.type === 1) {
            if (node.static || node.once) {
                node.staticInFor = isInFor;
            }
            // 作为静态节点 必须保证有子节点并且不为纯文本 否则更新消耗较大
            if (node.static && node.children.length && !(
                    node.children.length === 1 &&
                    node.children[0].type === 3
                )) {
                node.staticRoot = true;
                return
            } else {
                node.staticRoot = false;
            }
            // 递归处理子节点
            if (node.children) {
                for (var i = 0, l = node.children.length; i < l; i++) {
                    markStaticRoots(node.children[i], isInFor || !!node.for);
                }
            }
            // 无此属性
            if (node.ifConditions) {
                walkThroughConditionsBlocks(node.ifConditions, isInFor);
            }
        }
    }

  由于本例子节点是纯文本,所以staticRoot属性被标记为false。

 

  经过optimize函数,ast对象被添加了两个静态属性:

 

  

  最后是generate函数:

    // Line-8799
    function generate(ast, options) {
        // 保存上一个属性值
        var prevStaticRenderFns = staticRenderFns;
        var currentStaticRenderFns = staticRenderFns = [];
        var prevOnceCount = onceCount;
        onceCount = 0;
        currentOptions = options;
        warn$3 = options.warn || baseWarn;
        transforms$1 = pluckModuleFunction(options.modules, 'transformCode');
        dataGenFns = pluckModuleFunction(options.modules, 'genData');
        platformDirectives$1 = options.directives || {};
        isPlatformReservedTag$1 = options.isReservedTag || no;
        // 将ast对象转换为字符串
        var code = ast ? genElement(ast) : '_c("div")';
        staticRenderFns = prevStaticRenderFns;
        onceCount = prevOnceCount;
        return {
            render: ("with(this){return " + code + "}"),
            staticRenderFns: currentStaticRenderFns
        }
    }

  这个函数主要部分是code那一块,将ast对象转换成自定义的字符串形式,但是由于本例只有很简单的属性和文本,所以摘取分支看一下。

    // Line-8823
    function genElement(el) {
        if ( /* staticRoot,once,for,if,template,slot */ ) {
            /* code */
        } else {
            // component or element
            var code;
            if (el.component) {
                code = genComponent(el.component, el);
            } else {
                // 1
                var data = el.plain ? undefined : genData(el);
                // 2
                var children = el.inlineTemplate ? null : genChildren(el, true);
                code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
            }
            // module transforms
            for (var i = 0; i < transforms$1.length; i++) {
                code = transforms$1[i](el, code);
            }
            return code
        }
    }

  跳过所有属性的判断,直接进入最后的分支,在这里对节点与子节点分别做了处理。

  首先看genData函数。

    // Line-8937
    function genData(el) {
        var data = '{';

        if ( /* directives,key,ref,refInFor,pre,component */ ) {
            /* code */
        }
        // style,class
        for (var i = 0; i < dataGenFns.length; i++) {
            data += dataGenFns[i](el);
        }
        // 进入这个分支
        if (el.attrs) {
            data += "attrs:{" + (genProps(el.attrs)) + "},";
        }
        if ( /* props,events,nativeEvents,slotTarget,scopedSlots,model,inline-template */ ) {
            /* code */
        }
        data = data.replace(/,$/, '') + '}';
        // v-bind data wrap
        if (el.wrapData) {
            data = el.wrapData(data);
        }
        return data
    }

  可以看到跳过了大多数的判断,直接进入attrs,调用genProps函数并将之前{name:id,value:app}的对象传了进去。

    // Line-9146
    function genProps(props) {
        var res = '';
        // 将name,value拼接成 "name":"value", 形式的字符串
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ",";
        }
        // 去掉最后的逗号
        return res.slice(0, -1)
    }

  遍历attrs数组,将键值拼接成对应的字符串,本例只有一个id属性,拼接后返回。

  处理完后调用正则将最后的逗号去掉并加上对应的大括号,最后的wrapData属性也没有,所以直接返回data,最终结果如图所示:

 

  第一步完事后,进行子节点处理:

    // Line-8823
    function genElement(el) {
        if ( /* code... */ ) {
            /* code... */
        } else {
            var code;
            /* code... */
            // 返回节点信息
            var data = el.plain ? undefined : genData(el);
            // 子节点
            var children = el.inlineTemplate ? null : genChildren(el, true);
            code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
            // module transforms
            for (var i = 0; i < transforms$1.length; i++) {
                code = transforms$1[i](el, code);
            }
            return code
        }
    }
    // Line-8823
    function genChildren(el, checkSkip) {
        var children = el.children;
        if (children.length) {
            var el$1 = children[0];
            // 对简单的v-for做优化
            if (children.length === 1 &&
                el$1.for &&
                el$1.tag !== 'template' &&
                el$1.tag !== 'slot') {
                return genElement(el$1)
            }
            // 对存在子DOM节点的对象做处理 不存在返回0
            var normalizationType = checkSkip ? getNormalizationType(children) : 0;
            return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
        }
    }

    // Line-9107
    function genNode(node) {
        if (node.type === 1) {
            return genElement(node)
        } else {
            return genText(node)
        }
    }

    function genText(text) {
        // 进行包装
        return ("_v(" + (text.type === 2 ?
            text.expression // no need for () because already wrapped in _s()
            :
            transformSpecialNewlines(JSON.stringify(text.text))) + ")")
    }

  调用genChildren后同样返回一个包装后的字符串:

 

  最后,将节点与内容结合,生成一个总的字符串,如图所示:

  返回到generate函数:

    // Line-8799
    function generate(ast, options) {
        /* code */
        var code = ast ? genElement(ast) : '_c("div")';
        staticRenderFns = prevStaticRenderFns;
        onceCount = prevOnceCount;
        return {
            render: ("with(this){return " + code + "}"),
            staticRenderFns: currentStaticRenderFns
        }
    }

  输出一个对象,返回到最初的baseCompile函数,除了ast,多出来的对象内容如图:

  这个对象返回到compileToFunctions函数,目前进度是这样的:

    // Line-9326
    function compileToFunctions(template, options, vm) {
        options = options || {};

        /* new Function检测 */

        /* 缓存查询 */

        // compile
        var compiled = compile(template, options);

        /* 输出返回的error和tips */

        // 将字符串代码转化为函数
        var res = {};
        var fnGenErrors = [];
        res.render = makeFunction(compiled.render, fnGenErrors);
        var l = compiled.staticRenderFns.length;
        res.staticRenderFns = new Array(l);
        for (var i = 0; i < l; i++) {
            res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors);
        }

        /* checkError */

        return (functionCompileCache[key] = res)
    }

  返回的compiled如图所示:

  接下来将render字符串重新转换为函数,makeFunction方法很简单,就是使用new Function生成一个函数:

    // Line-9275
    function makeFunction(code, errors) {
        try {
            return new Function(code)
        } catch (err) {
            errors.push({
                err: err,
                code: code
            });
            return noop
        }
    }

  结果如图:

  由于res.staticRenderFns是空,所以最后直接把该res缓存进functionCompileCache然后返回。

 

  这个函数完事后,返回到了$mount方法中,很久之前的一个函数,内容如下:

    // Line-9553
    Vue$3.prototype.$mount = function(
        el,
        hydrating
    ) {
        el = el && query(el);

        /* warning */

        var options = this.$options;
        // resolve template/el and convert to render function
        if (!options.render) {
            var template = options.template;
            if (template) {
                /* 获取template */
            } else if (el) {
                template = getOuterHTML(el);
            }
            if (template) {
                /* compile start */
                if ("development" !== 'production' && config.performance && mark) {
                    mark('compile');
                }

                var ref = compileToFunctions(template, {
                    shouldDecodeNewlines: shouldDecodeNewlines,
                    delimiters: options.delimiters
                }, this);
                var render = ref.render;
                var staticRenderFns = ref.staticRenderFns;
                options.render = render;
                options.staticRenderFns = staticRenderFns;

                /* compile end */
            }
        }
        return mount.call(this, el, hydrating)
    };

  调用完compileToFunctions后,返回的对象包含一个render函数,一个staticRenderFns属性,分别挂载到options参数上,然后再次调用mount方法。

  结束~

  

posted @ 2017-06-13 15:07  书生小龙  阅读(1023)  评论(0编辑  收藏  举报