编译器思维导图
$mount重复定义的原因
/**
* 组件进行挂载
*
*/
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
//将mount进行缓存
var mount = Vue.prototype.$mount;
//$mount的重新定义
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
//编译器入口
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
return mount.call(this, el, hydrating)
};
从上面的源码可以看到vue对$mount的定义有个重复定义的过程,是解决runtime-only和runtime-compiler的问题,是否需要模板编译;
编译器生成
//编译器创建过程
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
var ast = parse(template.trim(), options);//生成抽象语法树 AST
if (options.optimize !== false) {
optimize(ast, options); //标注静态节点
}
var code = generate(ast, options); //生成渲染函数所需要的字符串
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});
- 执行parse,生成AST;
- 标注静态节点;
- 执行generate生成渲染函数所需字符串;
模板到AST
- parse完成了模板到AST的生成;
/**
*
* @param {*} template 解析模板
* @param {*} options 编译器参数
*/
function parse (
template,
options
) {
parseHTML(template, {
});
return root
}
- parse中主要执行了parseHTML;
/**
* 1. 解析模板,提取信息(提取单个),生成初步的AST对象
* 2. 调用钩子函数 start end chars comment 构建模板与AST之间的父子关系
*/
function parseHTML (html, options) {
var stack = [];//检测一个非一元标签是否缺少结束标签
var expectHTML = options.expectHTML;
var isUnaryTag?1 = options.isUnaryTag || no;//当前解析的标签是否为一元标签
var canBeLeftOpenTag?1 = options.canBeLeftOpenTag || no;
var index = 0;
var last, lastTag;//还未编译的字符串
//不会进入死循环的原因,正则方式建立token,提取信息,在原始字符串中删除
while (html) {
}
//删除html中匹配的token,如开始标签:<div
function advance (n) {
index += n;
html = html.substring(n);
}
//attribute 属性的正则文法
function parseStartTag () {
}
/**
* 辅助函数 对于特殊标签的处理如p 一个</p>浏览器自动生成<p></p>
* @param {} match
*/
function handleStartTag (match) {
}
}
静态节点标注
optimize的目标是遍历生成的模板AST树,检测纯静态的子树,即永远不需要更改的DOM。一旦我们检测到这些子树,我们可以:
- 把它们变成常数,这样我们就不需要了在每次重新渲染时为它们创建新的节点;
- 在修补过程中完全跳过它们;
以上方法判断是否是静态节点:
- 如果是纯文本,则标记为静态节点;
- 是否存在v-pre,如果存在,则不用解析;
- 不能存在 node.hasBindings,当节点有绑定 Vue属性的时候,比如指令,事件等,node.hasBindings 会为 true;
- 不能存在 node.if 和 node.for,同样,当 节点有 v-if 或者 v-for 的时候,node.if 或者 node.for 为true;
- 节点名称不能是slot 或者 component,因为这两者是要动态编译的,不属于静态范畴;
- isPlatformReservedTag(node.tag),isPlatformReservedTag 是用于判断该标签是否是正常的HTML 标签,有什么标签呢?
- isDirectChildOfTemplateFor(node) 表明了节点父辈以上所有节点不能是 带有v-for的template;
- Object.keys(node).every(isStaticKey),isStaticKey是一个函数,用于判断传入的属性是否在右边的范围内(type,tag,attrsList,attrsMap,plain,parent,children,attrs);
标记静态节点
标记静态根节点
渲染函数的生成
//渲染函数的生成
res.render = createFunction(compiled.render, fnGenErrors);
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err: err, code: code });
return noop
}
}