• .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方法。

      结束~

      

  • 相关阅读:
    利用vuex 做个简单的前端缓存
    EFcore 解决 SQLite 没有datetime 类型的问题
    dotnet 清理 nuget 缓存
    .net 5 单文件模式发布异常 CodeBase is not supported on assemblies loaded from a single-file bundle
    ubuntu 开启ip转发的方法
    Vue-ECharts 6 迁移记录
    System.Text.Json 5.0 已增加支持将Enum 由默认 Number类型 转换为String JsonStringEnumConverter
    Windows 10 LTSC 2019 正式版轻松激活教程
    Mac 提示Permission denied
    苹果手机代理 charles 提示(此链接非私人连接)
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/6991987.html
Copyright © 2020-2023  润新知