上节跑完了超长的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. JSP页面格式化数字或时间 基于jstl的

    jsp页面格式化数字或时间 转载自: http://blog.csdn.net/hakunamatata2008/archive/2011/01/21/6156203.aspx Tags fmt:re ...

  2. 如何维护一个1000 IP的免费代理池

    楔子 好友李博士要买房了, 前几天应邀帮他抓链家的数据分析下房价, 爬到一半遇到了验证码. 李博士的想法是每天把链家在售的二手房数据都抓一遍, 然后按照时间序列分析. 链家线上在交易的二手房数据大概有 ...

  3. Python文件读写模式

    r 打开只读文件,该文件必须存在. r+ 打开可读写的文件,该文件必须存在.可读,可写,可追加. w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失.若文件不存在则建立该文件. w+ 打 ...

  4. FPGA与PCI-E

    从并行到串行: PCI Express(又称PCIe)是一种高性能.高带宽串行通讯互连标准,取代了基于总线的通信架构,如:PCI.PCI Extended (PCI-X) 以及加速图形端口(AGP). ...

  5. Python面试题之python是一种什么语言及优缺点

    1.说说python是一种什么语言? 参考答案:python是一门动态解释性的强类型定义语言 编译型vs解释型 编译型优点:编译器一般会有预编译的过程对代码进行优化.因为编译只做一次,运行时不需要编译 ...

  6. Linux常见命令集锦

    这是平常用到的命令在这里做一下总结: 一.python 类1.pip(已安装)pip用来安装来自PyPI(https://www.python.org/)的python所有的依赖包,并且可以选择安装任 ...

  7. git 忽略文件夹

    $ vim .gitignore 添加要忽略的文件或文件夹 esc + :wq 退出vim命令行

  8. python中ConfigParse模块的用法

    ConfigParser 是Python自带的模块, 用来读写配置文件, 用法及其简单. 配置文件的格式是: [...]包含的叫section section 下有option=value这样的键值 ...

  9. 小程序解释HTML富文本的两种办法

    今天写着着代码,读取数据库的内容时突然跳出"<span>.<p>. "这些HTML标签.字符,吓一跳:本来如果是写HTML.JS倒也没什么,但是我在写小程序 ...

  10. Weave Scope 容器地图 - 每天5分钟玩转 Docker 容器技术(80)

    Weave Scope 的最大特点是会自动生成一张 Docker 容器地图,让我们能够直观地理解.监控和控制容器.千言万语不及一张图,先感受一下. 下面开始实践 Weave Scope. 安装 执行如 ...