.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 {
//
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)的更多相关文章
- 大白话Vue源码系列(03):生成AST
阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...
- [Vue源码]一起来学Vue模板编译原理(一)-Template生成AST
本文我们一起通过学习Vue模板编译原理(一)-Template生成AST来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vu ...
- [Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串
本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学V ...
- Vue源码后记-其余内置指令(3)
其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...
- 大白话Vue源码系列(02):编译器初探
阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...
- 大白话Vue源码系列(03):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
- 大白话Vue源码系列(04):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
- 大白话Vue源码系列(05):运行时鸟瞰图
阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...
- 入口文件开始,分析Vue源码实现
Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...
- 入口开始,解读Vue源码(一)-- 造物创世
Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...
随机推荐
- MySQL集群(三)mysql-proxy搭建负载均衡与读写分离
前言 前面学习了主从复制和主主复制,接下来给大家分享一下怎么去使用mysql-proxy这个插件去配置MySQL集群中的负载均衡以及读写分离. 注意:这里比较坑的就是mysql-proxy一直没有更新 ...
- SharedPreferences的使用
- 反射结合xml简单的模拟spring创建bean
框架最底层其实就是通过反射实现的,就像spring,当你配置各种各样的bean时都是以配置文件的形式配置的,你需要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,这儿写一个简单的 ...
- Java并发之线程中断
前面的几篇文章主要介绍了线程的一些最基本的概念,包括线程的间的冲突及其解决办法,以及线程间的协作机制.本篇主要来学习下Java中对线程中断机制的实现.在我们的程序中经常会有一些不达到目的不会退出的线程 ...
- POJ1422Air Raid(二分图,最小不相交路径覆盖)
Air Raid Consider a town where all the streets are one-way and each street leads from one intersecti ...
- Ionic3学习笔记(三)禁止横屏
本文为原创文章,转载请标明出处 目录 安装 使用 参数 1. 安装 命令行输入 ionic cordova plugin add cordova-plugin-screen-orientation n ...
- MVC3/4/5/6 布局页及Razor语法及Route路由配置
一.目录结构 二.Razor语法 代码块:@{},如:@{Html.Raw(“”);} @if(){} @switch(){} @for(){} @foreach(){} @while(){} @do ...
- zoj1871steps 数学 水
zoj1871 题目大意 ...
- QQ--基于TCP/UDP协议的通讯原理
QQ是一个基于TCP/UDP协议的通讯软件 发送消息的时候是UDP打洞,登陆的时候使用HTTP~因为登陆服务器其实就是一个HTTP服 务器,只不过不是常用的那些,那个服务器是腾讯自行开发的! 一 ...
- Jquery仿京东分类导航层简单实现
<script src="/js/jquery-1.11.1.min.js" type="text/javascript"></script& ...