上节跑完了超长的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. MySQL集群(三)mysql-proxy搭建负载均衡与读写分离

    前言 前面学习了主从复制和主主复制,接下来给大家分享一下怎么去使用mysql-proxy这个插件去配置MySQL集群中的负载均衡以及读写分离. 注意:这里比较坑的就是mysql-proxy一直没有更新 ...

  2. SharedPreferences的使用

  3. 反射结合xml简单的模拟spring创建bean

    框架最底层其实就是通过反射实现的,就像spring,当你配置各种各样的bean时都是以配置文件的形式配置的,你需要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,这儿写一个简单的 ...

  4. Java并发之线程中断

    前面的几篇文章主要介绍了线程的一些最基本的概念,包括线程的间的冲突及其解决办法,以及线程间的协作机制.本篇主要来学习下Java中对线程中断机制的实现.在我们的程序中经常会有一些不达到目的不会退出的线程 ...

  5. POJ1422Air Raid(二分图,最小不相交路径覆盖)

    Air Raid Consider a town where all the streets are one-way and each street leads from one intersecti ...

  6. Ionic3学习笔记(三)禁止横屏

    本文为原创文章,转载请标明出处 目录 安装 使用 参数 1. 安装 命令行输入 ionic cordova plugin add cordova-plugin-screen-orientation n ...

  7. MVC3/4/5/6 布局页及Razor语法及Route路由配置

    一.目录结构 二.Razor语法 代码块:@{},如:@{Html.Raw(“”);} @if(){} @switch(){} @for(){} @foreach(){} @while(){} @do ...

  8. zoj1871steps 数学 水

                                                                                            zoj1871 题目大意 ...

  9. QQ--基于TCP/UDP协议的通讯原理

    QQ是一个基于TCP/UDP协议的通讯软件  发送消息的时候是UDP打洞,登陆的时候使用HTTP~因为登陆服务器其实就是一个HTTP服 务器,只不过不是常用的那些,那个服务器是腾讯自行开发的!   一 ...

  10. Jquery仿京东分类导航层简单实现

    <script src="/js/jquery-1.11.1.min.js" type="text/javascript"></script& ...