持续上一篇的节奏,上一篇咱们剖析了Vue3render函数的生成进程,在genNode函数中,咱们看到了许多节点类型的生成函数;

今日咱们就来详细剖析一下这些节点类型的生成函数,看看它们是怎么生成的;、

genNode

咱们先简略的回顾一下上一篇的genNode函数,详细能够看上一篇的文章:【源码&库】Vue3模版解析后的AST转化为render函数的进程

function genNode(node, context) {
    // 假如是 string 类型,则直接作为代码
    if (isString(node)) {
        context.push(node);
        return;
    }
    // 假如是 symbol 类型,则运用辅佐函数生成代码
    if (isSymbol(node)) {
        context.push(context.helper(node));
        return;
    }
    // 依据节点类型,履行不同的生成函数
    switch (node.type) {
        case 1: // 元素节点
        case 9: // if
        case 11: // for
            // 这些节点直接递归生成
            assert(
                node.codegenNode != null,
                `Codegen node is missing for element/if/for node. Apply appropriate transforms first.`
            );
            genNode(node.codegenNode, context);
            break;
        case 2: // 文本节点
            genText(node, context);
            break;
        case 4: // 表达式节点
            genExpression(node, context);
            break;
        case 5: // 插值节点
            genInterpolation(node, context);
            break;
        case 12: // fragment 节点
            genNode(node.codegenNode, context);
            break;
        case 8: // 复合表达式节点
            genCompoundExpression(node, context);
            break;
        case 3: // 注释节点
            genComment(node, context);
            break;
        case 13: // 生成 createVNode 的调用
            genVNodeCall(node, context);
            break;
        case 14: // 生成一般函数调用
            genCallExpression(node, context);
            break;
        case 15: // 生成目标表达式
            genObjectExpression(node, context);
            break;
        case 17: // 生成数组表达式
            genArrayExpression(node, context);
            break;
        case 18: // 生成函数表达式
            genFunctionExpression(node, context);
            break;
        case 19: // 生成条件表达式
            genConditionalExpression(node, context);
            break;
        case 20: // 生成缓存表达式
            genCacheExpression(node, context);
            break;
        case 21: // 生成节点列表
            genNodeList(node.body, context, true, false);
            break;
        case 22:
            break;
        case 23:
            break;
        case 24:
            break;
        case 25:
            break;
        case 26:
            break;
        case 10:
            break;
        default:
        {
            assert(false, `unhandled codegen node type: ${node.type}`);
            const exhaustiveCheck = node;
            return exhaustiveCheck;
        }
    }
}

在这儿咱们能够看到有许多的节点类型,如下:

  • genText: 文本节点
  • genExpression: 表达式节点
  • genInterpolation: 插值节点
  • genCompoundExpression: 复合表达式节点
  • genComment: 注释节点
  • genVNodeCall: 生成 createVNode 的调用
  • genCallExpression: 生成一般函数调用
  • genObjectExpression: 生成目标表达式
  • genArrayExpression: 生成数组表达式
  • genFunctionExpression: 生成函数表达式
  • genConditionalExpression: 生成条件表达式
  • genCacheExpression: 生成缓存表达式
  • genNodeList: 生成节点列表

咱们就来一一剖析一下这些节点类型的生成函数,咱们这一章仍是一样能够经过如下示例代码进行调试和剖析:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id='app'></div>
</body>
<script src="./vue.global.js"></script>
<script>
    const {createApp, h} = Vue;
    const app = createApp({
        template: '直接在这儿写模板语法'
    });
    debugger;
    app.mount('#app');
</script>
</html>

genText

genText函数用来生成文本节点,详细代码如下:

function genText(node, context) {
    // 假如是纯文本,则直接经过 JSON.stringify 转成字符串
    // 最终的结果是 return "xxx"
    context.push(JSON.stringify(node.content), node);
}

验证代码如下:

const app = createApp({
    template: 'xxx'
});

生成的代码如下:

return function render(_ctx, _cache) {
  with (_ctx) {
    return "xxx"
  }
}

genExpression

genExpression函数用来生成表达式节点,详细代码如下:

function genExpression(node, context) {
    // 获取表达式 和 是否静态标识
    const { content, isStatic } = node;
    // 假如是静态的,直接将内容作为字符串进行处理,不然就直接将表达式进行返回
    context.push(isStatic ? JSON.stringify(content) : content, node);
}

验证代码如下:

const app = createApp({
    template: '{{ 1 + 1 }}'
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString } = _Vue
    return _toDisplayString(1 + 1)
  }
}

genInterpolation

能够看到上面的终究生成的结果会有一个toDisplayString的函数包装,这个函数是由genInterpolation函数生成的;

而这个函数便是由genInterpolation函数生成的,这个函数便是用来处理插值表达式的,详细代码如下:

function genInterpolation(node, context) {
    // 从上下文中获取 push、helper 和 pure
    const { push, helper, pure } = context;
    // pure 用来符号是否是纯的,假如是纯的,则会增加 PURE_ANNOTATION
    // 也便是 /*#__PURE__*/ 符号
    // 有了这个符号,在代码优化阶段,就会告诉编译器,能够安全地删去或替换它的调用,而不影响程序的行为
    if (pure)
      push(PURE_ANNOTATION);
    // TO_DISPLAY_STRING 便是生成 toDisplayString 函数的辅佐函数
    // 经过调用 helper(TO_DISPLAY_STRING) 就会生成:_toDisplayString
    push(`${helper(TO_DISPLAY_STRING)}(`);
    // 递归生成插值表达式的内容,依照咱们的示例就会进入上面的 genExpression 函数
    genNode(node.content, context);
    // 最终将 ) 增加到代码中
    push(`)`);
}

验证代码和结果同上;

genCompoundExpression

复合表达式节点指的是一个节点中包括多个表达式,例如:{{ 1 + 1 }} {{ 2 + 2 }},这儿便是一个复合表达式节点,这个节点中包括了两个表达式,分别是 1 + 12 + 2
只要是一个节点中包括多个表达式,那么这个节点便是一个复合表达式节点;

genCompoundExpression函数用来生成复合表达式节点,详细代码如下:

function genCompoundExpression(node, context) {
    // 直接遍历子节点
    for (let i = 0; i < node.children.length; i++) {
        const child = node.children[i];
        // 假如是 string 类型,则直接推入到上下文中
        if (isString(child)) {
            context.push(child);
        }
        // 不然就递归调用 genNode 函数
        else {
            genNode(child, context);
        }
    }
}

验证代码如下:

const app = createApp({
    template: '{{ 1 + 1 }} {{ 2 + 2 }}'
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
    with (_ctx) {
        const { toDisplayString: _toDisplayString } = _Vue
        return _toDisplayString(1 + 1) + " " + _toDisplayString(2 + 2)
    }
}

genComment

genComment函数用来生成注释节点,详细代码如下:

function genComment(node, context) {
    // 同上
    const { push, helper, pure } = context;
    if (pure) {
        push(PURE_ANNOTATION);
    }
    // helper(CREATE_COMMENT) 用来生成 _createCommentVNode 函数
    // 将注释内容转化为 JSON 格式的字符串作为内容
    push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);
}

验证代码如下:

const app = createApp({
    template: '<!-- 这是注释 -->'
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    const { createCommentVNode: _createCommentVNode } = _Vue
    return _createCommentVNode(" 这是注释 ")
  }
}

genVNodeCall

genVNodeCall函数用来生成复合表达式节点,详细代码如下:

function genVNodeCall(node, context) {
    // 获取 push、helper 和 pure
    const { push, helper, pure } = context;
    // 获取节点的类型和 props
    const {
        tag,
        props,
        children,
        patchFlag,
        dynamicProps,
        directives,
        isBlock,
        disableTracking,
        isComponent
    } = node;
    // 是否存在指令,假如存在,则需求增加 WITH_DIRECTIVES 符号
    // 最终会生成:_withDirectives
    if (directives) {
        push(helper(WITH_DIRECTIVES) + `(`);
    }
    // 是否是一个块,块指的是有能成对呈现的节点,像 html 标签都是
    // 也会有自闭合标签,像 img、input 等,可是他们也都是成块的
    // 所以我理解的块指的是不行拆分的一个整体就表明为一个块
    // 这儿最终会生成:_openBlock
    if (isBlock) {
        push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `);
    }
    // 是否纯函数
    if (pure) {
        push(PURE_ANNOTATION);
    }
    // 依据是否是块节点和是否是组件选择恰当的 createVNode 辅佐函数
    // 这儿最终就便是会生成不同的函数处理函数,能够看下面贴出来的 getVNodeBlockHelper 和 getVNodeHelper 函数
    const callHelper = isBlock ? getVNodeBlockHelper(context.inSSR, isComponent) : getVNodeHelper(context.inSSR, isComponent);
    push(helper(callHelper) + `(`, node);
    // 生成包括 createVNode 调用的参数列表
    // genNullableArgs 用于生成可空的参数列表,这个函数便是会移除尾部的控制,然后将剩下的参数列表返回
    // 例如 ['div', null, [], null, null] 会生成 ['div', null, []]
    // 然后 ['div', null, []] 这个便是一个函数的参数列表,例如 fn('div', null, []) 这样的调用
    // 后边会有 genNodeList 函数的讲解
    genNodeList(
        genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
        context
    );
    // 推入函数的完毕括号
    push(`)`);
    // 假如是块节点,则需求推入块节点的完毕括号
    if (isBlock) {
        push(`)`);
    }
    // 假如存在指令,则推入逗号和生成的指令函数,实质也是经过递归调用 genNode 函数
    if (directives) {
        push(`, `);
        genNode(directives, context);
        push(`)`);
    }
}
function getVNodeHelper(ssr, isComponent) {
    return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE;
}
function getVNodeBlockHelper(ssr, isComponent) {
    return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK;
}

验证代码如下:

const app = createApp({
    template: '<div>xxx</div>'
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
    return (_openBlock(), _createElementBlock("div", null, "xxx"))
  }
}

genCallExpression

一般函数调用自身不是由开发者直接书写的,而是内部处理的,例如运用 v-if 会生成一个注释节点来进行符号(假如节点躲藏,没有代替的节点),这个时分就会生成 _createCommentVNode 函数的调用;

genCallExpression函数用来生成一般函数调用,详细代码如下:

function genCallExpression(node, context) {
    // 获取 push、helper 和 pure
    const { push, helper, pure } = context;
    // 这一步是生成调用函数名
    const callee = isString(node.callee) ? node.callee : helper(node.callee);
    // 纯
    if (pure) {
        push(PURE_ANNOTATION);
    }
    // 推入函数名和左括号
    push(callee + `(`, node);
    // 这一步能够理解为生成调用函数的参数列表
    genNodeList(node.arguments, context);
    // 推入右括号
    push(`)`);
}

验证代码如下:

const app = createApp({
    template: '<div  v-if="true">xxx</div>',
});

生成的代码如下:

const _Vue = Vue
const { createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = { key: 0 }
return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
    return true
      ? (_openBlock(), _createElementBlock("div", _hoisted_1, "xxx"))
      : _createCommentVNode("v-if", true)
  }
}

genObjectExpression

目标表达式指的是一个目标的键值对,例如咱们运用的 @click=”xxx” 最终的解释会变成 onCLick: xxx,这儿的 onClick 便是一个目标表达式;
还有其他的状况,例如 v-bind 也会生成一个目标表达式;

genObjectExpression函数用来生成目标表达式,详细代码如下:

function genObjectExpression(node, context) {
    // 取出一些辅佐函数
    const { push, indent, deindent, newline } = context;
    // 取出 properties 特点列表
    const { properties } = node;
    // 假如没有特点,则直接推入 {},表明空目标
    if (!properties.length) {
        push(`{}`, node);
        return;
    }
    // 判断是否多行,假如是多行,则需求增加缩进
    const multilines = properties.length > 1 || properties.some((p) => p.value.type !== 4);
    push(multilines ? `{` : `{ `);
    multilines && indent();
    // 遍历特点列表
    for (let i = 0; i < properties.length; i++) {
        const { key, value } = properties[i];
        // 生成 key
        genExpressionAsPropertyKey(key, context);
        push(`: `);
        // 生成 value
        genNode(value, context);
        // 假如不是最终一个,则推入逗号和换行
        if (i < properties.length - 1) {
            push(`,`);
            newline();
        }
    }
    // 假如是多行,则需求削减缩进
    multilines && deindent();
    // 推入右括号
    push(multilines ? `}` : ` }`);
}

验证代码如下:

const app = createApp({
    template: '<div @click="() => {}">xxx</div>'
});

生成的代码如下:

const _Vue = Vue
const {  } = _Vue
const _hoisted_1 = ["onClick"]
return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
    return (_openBlock(), _createElementBlock("div", { onClick: () => {} }, "xxx", 8 /* PROPS */, _hoisted_1))
  }
}

genArrayExpression

数组表达式和目标表达式相似,只不过是一个数组的形式,可用于自定义指令的参数列表;

genArrayExpression函数用来生成数组表达式,详细代码如下:

function genArrayExpression(node, context) {
    // 内部是调用 genNodeListAsArray 函数
    genNodeListAsArray(node.elements, context);
}
function genNodeListAsArray(nodes, context) {
    // 判断是否多行,多行就增加缩进
    const multilines = nodes.length > 3 || nodes.some((n) => isArray(n) || !isText(n));
    context.push(`[`);
    multilines && context.indent();
    // 仍是运用 genNodeList 来生成数据
    genNodeList(nodes, context, multilines);
    multilines && context.deindent();
    context.push(`]`);
}

验证代码如下:

const app = createApp({
    template: '<div v-xxx="[xxx, yyy]">{{i}}</div>',
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue
    const _directive_xxx = _resolveDirective("xxx")
    return _withDirectives((_openBlock(), _createElementBlock("div", null, [
      _createTextVNode(_toDisplayString(i), 1 /* TEXT */)
    ])), [
      [_directive_xxx, [xxx, yyy]]
    ])
  }
}

genFunctionExpression

函数表达式指的是需求用函数来处理的状况,表明内容现已是不行控了,例如 v-for、插槽等不行控的内容;
v-if 并不会生成函数表达式,因为 v-if 只是一个条件,最终会生成一个三元表达式;

genFunctionExpression函数用来生成函数表达式,详细代码如下:

function genFunctionExpression(node, context) {
    // 辅佐函数
    const {push, indent, deindent} = context;
    // 节点的一些特点
    const {params, returns, body, newline, isSlot} = node;
    // 假如是插槽函数,增加 _withCtx 辅佐函数
    if (isSlot) {
        push(`_${helperNameMap[WITH_CTX]}(`);
    }
    // 推入函数的左括号,箭头函数的开头
    push(`(`, node);
    // 假如参数是一个数组,则递归生成参数列表
    if (isArray(params)) {
        genNodeList(params, context);
    }
    // 不然就运用 genNode 生成参数
    else if (params) {
        genNode(params, context);
    }
    // 推入函数的右括号,箭头函数的完毕
    push(`) => `);
    if (newline || body) {
        push(`{`);
        indent();
    }
    // 假如有返回值,则生成返回值
    if (returns) {
        // 假如需求换行,输出 return
        // 箭头函数假如没有花括号,能够直接返回,所以这儿需求增加 return
        if (newline) {
            push(`return `);
        }
        // 假如返回值是数组,调用 genNodeListAsArray 生成返回值列表的代码
        if (isArray(returns)) {
            genNodeListAsArray(returns, context);
        }
        // 不然就调用 genNode 生成返回值
        else {
            genNode(returns, context);
        }
    } 
    // 没有返回值,但存在函数体
    else if (body) {
        genNode(body, context);
    }
    // 假如需求换行,增加换行和缩进
    if (newline || body) {
        deindent();
        push(`}`);
    }
    // 插槽函数,输出插槽函数的完毕符号
    if (isSlot) {
        push(`)`);
    }
}

验证代码如下:

const app = createApp({
    template: '<div v-for="i in 10">{{i}}</div>',
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString } = _Vue
    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(10, (i) => {
      return (_openBlock(), _createElementBlock("div", null, _toDisplayString(i), 1 /* TEXT */))
    }), 256 /* UNKEYED_FRAGMENT */))
  }
}

genConditionalExpression

条件表达式指的是 v-if、v-else-if、v-else 等条件句子;

genConditionalExpression函数用来生成条件表达式,详细代码如下:

function genConditionalExpression(node, context) {
    // 各种状况的特点
    const { test, consequent, alternate, newline: needNewline } = node;
    // 辅佐函数
    const { push, indent, deindent, newline } = context;
    // 假如测验条件是一个字符串文本
    if (test.type === 4) {
        // 假如字符串内容不是一个简略的标识符,就需求加括号
        const needsParens = !isSimpleIdentifier(test.content);
        needsParens && push(`(`);
        // 上面的 genExpression 函数便是用来生成表达式的
        genExpression(test, context);
        needsParens && push(`)`);
    } 
    // 假如测验条件是一个复杂的表达式
    else {
      push(`(`);
      // 递归调用 genNode 函数
      genNode(test, context);
      push(`)`);
    }
    // 假如需求换行,增加缩进
    needNewline && indent();
    // 缩进等级增加
    context.indentLevel++;
    needNewline || push(` `);
    // 三元表达式的问号
    push(`? `);
    // 递归调用 genNode 函数,这是条件为真的状况
    genNode(consequent, context);
    // 缩进等级削减
    context.indentLevel--;
    needNewline && newline();
    needNewline || push(` `);
    // 三元表达式的冒号
    push(`: `);
    // 假如是嵌套的条件表达式,需求增加缩进等级
    const isNested = alternate.type === 19;
    if (!isNested) {
      context.indentLevel++;
    }
    // 这儿是条件为假的状况
    genNode(alternate, context);
    // 假如是嵌套的条件表达式,需求削减缩进等级
    if (!isNested) {
      context.indentLevel--;
    }
    // 假如需求换行,履行削减缩进的操作(不带换行)
    needNewline && deindent(
      true
      /* without newline */
    );
}

验证代码如下:

const app = createApp({
    template: '<div v-if="true">xxx</div>',
});

生成的代码如下:

const _Vue = Vue
const { createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = { key: 0 }
return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
    return true
      ? (_openBlock(), _createElementBlock("div", _hoisted_1, "123"))
      : _createCommentVNode("v-if", true)
  }
}

genCacheExpression

缓存表达式首要运用于v-once指令,这个指令会将节点缓存起来,不会再进行更新;

genCacheExpression函数用来生成缓存表达式,详细代码如下:

function genCacheExpression(node, context) {
    // 辅佐函数
    const {push, helper, indent, deindent, newline} = context;
    // 这儿是生成缓存的 key
    push(`_cache[${node.index}] || (`);
    // 假如是 vnode 节点,则需求增加缩进和设置追寻符号
    // 这儿设置为 -1,表明不需求追寻
    if (node.isVNode) {
        indent();
        push(`${helper(SET_BLOCK_TRACKING)}(-1),`);
        newline();
    }
    // 设置缓存的值
    push(`_cache[${node.index}] = `);
    // 仍是递归调用 genNode 函数来生成缓存的值
    genNode(node.value, context);
    // 启用缓存的追寻符号,并返回缓存的值
    if (node.isVNode) {
        push(`,`);
        newline();
        push(`${helper(SET_BLOCK_TRACKING)}(1),`);
        newline();
        push(`_cache[${node.index}]`);
        deindent();
    }
    // 最终推入右括号
    push(`)`);
}

验证代码如下:

const app = createApp({
    template: '<div v-once>xxx</div>',
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    const { setBlockTracking: _setBlockTracking, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, createElementVNode: _createElementVNode } = _Vue
    return _cache[0] || (
      _setBlockTracking(-1),
      _cache[0] = _createElementVNode("div", null, [
        _createTextVNode(_toDisplayString("xxx"), 1 /* TEXT */)
      ]),
      _setBlockTracking(1),
      _cache[0]
    )
  }
}

genNodeList

实质上genNodeList函数是处理所有 list 类型的数据,在上述的许多函数中都会运用到;
本事例为了能更清晰的看到它的效果,能在genNode中看到它的调用,会运用 v-for + v-memo 来进行验证;
v-memo 指令的效果不在本文章中进行讲解,需求的能够自行查阅资料;

genNodeList函数用来生成节点列表,详细代码如下:

function genNodeList(nodes, context, multilines = false, comma = true) {
    // 辅佐函数
    const {push, newline} = context;
    // 直接遍历节点
    for (let i = 0; i < nodes.length; i++) {
        // 获取当时节点
        const node = nodes[i];
        // 假如是字符串,则直接推入
        if (isString(node)) {
            push(node);
        } 
        // 假如是数组,则递归调用 genNodeListAsArray 函数
        // genNodeListAsArray 函数在上面现已呈现过了,这儿就不再赘述了
        else if (isArray(node)) {
            genNodeListAsArray(node, context);
        } 
        // 不然就递归调用 genNode 函数
        else {
            genNode(node, context);
        }
        // 假如不是最终一个
        if (i < nodes.length - 1) {
            // 假如是多行,则推入逗号和换行
            if (multilines) {
                comma && push(",");
                newline();
            } 
            // 不然就推入逗号和空格
            else {
                comma && push(", ");
            }
        }
    }
}

验证代码如下:

const app = createApp({
    template: '<div v-for="i in 10" v-memo="[i]">i</div>',
});

生成的代码如下:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createTextVNode: _createTextVNode, isMemoSame: _isMemoSame, withMemo: _withMemo } = _Vue
    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(10, (i, __, ___, _cached) => {
      const _memo = ([i])
      if (_cached && _isMemoSame(_cached, _memo)) return _cached
      const _item = (_openBlock(), _createElementBlock("div", null, [
        _createTextVNode("i")
      ]))
      _item.memo = _memo
      return _item
    }, _cache, 0), 256 /* UNKEYED_FRAGMENT */))
  }
}

总结

本章节首要讲解了genNode函数,这个函数是用来生成节点的,这个函数会依据节点的类型来调用不同的函数来生成节点,例如文本节点、表达式节点、插值节点等等;

而经过这一章也了解到了巨量的Vue的模版语法能够书写的方式,其中的许多细节也只要翻源码才知道原来还能够这样玩;

到这儿咱们的模板到AST的转化,在从AST的转化到render函数的代码生成,现已全部完成了;

这一块足足花了五章的内容来进行剖析,信息量是巨大的,一起也算是完成了一个里程碑,收获满满,成就感也满满;

前史章节