上节跑完了超长的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 {
//
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
}
}

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

  首先看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方法。

  结束~

  

.9-Vue源码之AST(5)的更多相关文章

  1. 大白话Vue源码系列(03):生成AST

    阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...

  2. [Vue源码]一起来学Vue模板编译原理(一)-Template生成AST

    本文我们一起通过学习Vue模板编译原理(一)-Template生成AST来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vu ...

  3. [Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串

    本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学V ...

  4. Vue源码后记-其余内置指令(3)

    其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...

  5. 大白话Vue源码系列(02):编译器初探

    阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...

  6. 大白话Vue源码系列(03):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  7. 大白话Vue源码系列(04):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  8. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

  9. 入口文件开始,分析Vue源码实现

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

  10. 入口开始,解读Vue源码(一)-- 造物创世

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

随机推荐

  1. temp--达州银行

    达州银行现场 服务器IP地址 192.168.1.234 ilink / ilink 自己电脑需要设置为固定IP 192.168.1.XXX 子网掩码 255.255.255.0 192.168.1. ...

  2. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  3. Failed to load the JNI shared library "XXXXXXX"

    今天启动Eclipse的时候出现了这个问题,经过查找, 一般来说这种问题都是因为eclipse 和Java 的兼容性不一致所导致的. 1) 查看Eclipse 和Java 版本 那么我们需要分别查看下 ...

  4. Thinkphp3.2版本使用163邮箱发(验证码)邮件

    今天忽然想写一个用户修改密码的功能,又没有短信接口,只能选择用邮箱发送验证码啦,穷啊,没办法,哈哈,以下为正文. ------------------------------------------- ...

  5. javascript 单元测试初入门

    1.使用mocha工具实现单元测试 ①首先准备node环境 ②安装mocha:npm install mocha 也可以进行全局安装 npm install global mocha ③安装断言库:n ...

  6. JVM(五)内存(Heap)分配

    前面的两小节,我分享了一下JVM的垃圾回收算法和垃圾回收器,本节中,我们来看看JVM的内存分配到底是如何进行的,作为对前面两节内存回收的补充. 从前面的内存回收中我们了解到,Hotspot JVM中的 ...

  7. Spring Boot Document Part II(下)

    Part II. Getting started 11. 开发第一个Spirng Boot Application使用Spring Boot的关键特征开发一个基于JAVA Web的“Hello Wor ...

  8. 快速搭建应用服务日志收集系统(Filebeat + ElasticSearch + kibana)

    快速搭建应用服务日志收集系统(Filebeat + ElasticSearch + kibana) 概要说明 需求场景,系统环境是CentOS,多个应用部署在多台服务器上,平时查看应用日志及排查问题十 ...

  9. 51nod 1118 机器人走方格 解题思路:动态规划 & 1119 机器人走方格 V2 解题思路:根据杨辉三角转化问题为组合数和求逆元问题

    51nod 1118 机器人走方格: 思路:这是一道简单题,很容易就看出用动态规划扫一遍就可以得到结果, 时间复杂度O(m*n).运算量1000*1000 = 1000000,很明显不会超时. 递推式 ...

  10. vue-chat项目之重构与体验优化

    前言 vue-chat 也就是我的几个月之前写的一个基于vue的实时聊天项目,到目前为止已经快满400star了,注册量也已经超过了1700+,消息量达2000+,由于一直在实习,没有时间对它频繁地更 ...