vue的模板编译的过程
- 获取template
- template -> AST树
- AST树 -> render函数
- render函数 -> 虚拟节点
- 设置patch -> 打补丁到真实的DOM
AST Abstract syntax tree 抽象语法树 源代码的抽象语法结构的树状描述
思路
/*
<div id="app" style="color: red;font-size: 20px;">
你好, {{ name }}
<span class="text" style="color: green">{{ age }}</span>
</div>
type: 1元素
type: 3文本节点
匹配完了就删除 match来匹配
删除完了继续匹配
<div
id="app" style="color: red;font-size: 20px;"
>
你好, {{ name }}
<span
class="text"
style="color: green"
>
{{ age }}
</span>
</div>
*/
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app" style="color: red;font-size: 20px;">
你好, {{ name }}
<span class="text" style="color: green">{{ age }}</span>
</div>
<script src="dist/vue.js"></script>
<script>
// umd -> window.Vue
// console.log(Vue);
const vm = new Vue({
el: "#app",
data() {
return {
name: "蒋小白",
age: 18,
msg: 'vue zzz',
a: {
b: 10
},
list: [1,2, 3]
}
},
props: {},
watch: {},
computed: {}
})
// .$mount('#app');
</script>
</body>
</html>
入口文件
import { initMixin } from './init'
function Vue(options) {
// 初始化
this.__init(options);
}
initMixin(Vue);//初始化
export default Vue
初始化文件
import { initState } from './initState.js';
import { compileToFunction } from './compile/index.js'
// 初始化业务
export function initMixin(Vue) {
// 初始化
Vue.prototype.__init = function(options) {
let vm = this;// this -> vue实例
vm.$options = options;
// 初始化状态
initState(vm);
//------------------------- 上面的代码见前几节的学习笔记 ----------------
// 渲染模板 el
if(vm.$options.el) {
// 执行挂载函数 Vue.prototype.$mount原型上的函数
vm.$mount(vm.$options.el);
}
}
// 创建 $mount
Vue.prototype.$mount = function(el) {
// el < template < render 优先级别比较
let vm = this,
options = vm.$options;
el = document.querySelector(el);//获取元素
vm.$el = el;//为什么在实例上挂载,就是方便以后传实例可以获取所有的东西
if(!options.render) {//没有render
// 获取template属性
let template = options.template;
if(!template && el) {// 没有template && 有el
// 获取包括自己的html
template = el.outerHTML;// <div id="app">{{msg}}</div>
// 变成 ast语法树
}
//作用 template -> ast树 -> 转成render函数
let render = compileToFunction(template);
// 将render挂载到options上面
options.render = render;
}
}
}
处理 tempalte -> ast树的入口文件 compile/index.js
import { parseHtmlToAst } from './astParser';
// 进行模板编译的
function compileToFunction(html) {
// html -> 转化成Ast
const ast = parseHtmlToAst(html);// 源码叫 parseHTML
}
export {
compileToFunction
}
tempalte -> ast树功能文件 compile/astParser.js
// id="app" id='app' id=app
// const attribute = /^\s*([^\s"'<div>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<div>`]+)))?/;
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
// 匹配标签名
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
// 匹配特殊的 <my-header></my-header> <m:header></m:header>
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 匹配标签头 </div
const startTagOpen = new RegExp(`^<${qnameCapture}`);
// 匹配标签尾 > />
const startTagClose = /^\s*(\/?)>/;
// 匹配</div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// html -> 转化成Ast
function parseHtmlToAst(html) {
let text,
root,
currentParent,
stack = [];
while(html) {
// 先找 <
let textEnd = html.indexOf('<');
if(textEnd === 0) {//说明是第一项
const startTagMatch = parseStartTag();
if(startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
// 匹配如 </div>
const endTagMatch = html.match(endTag);
if(endTagMatch) {
advance(endTagMatch[0].length);
end(endTagMatch[1]);
continue;
}
}
// < 不在第一位,说明 -> 文本 + <
if(textEnd > 0) {
// 获取文本
text = html.substring(0, textEnd);
}
if(text) {
// 删掉text
advance(text.length);
// 处理文本数据格式
chars(text);
}
}
function parseStartTag() {
const start = html.match(startTagOpen);//匹配 如 <div
let end,
attr;
if(start) {
const match = {
tagName: start[1],
attrs: [],//属性还没处理
}
// 截取后面的的部分,去掉匹配的部门
advance(start[0].length);
// 没有匹配> 匹配的属性
while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
match.attrs.push({
name: attr[1],
value: attr[3] || attr[4] || attr[5],
// 写法不同 值可能出现在 3 4 5 项上的一项上
})
// 截取掉匹配的部分
advance(attr[0].length);
}
if(end) {
advance(end[0].length);
return match;
}
}
}
// 截取字符串
function advance(n) {
html = html.substring(n);
}
// stack [div, span]
function start(tagName, attrs) {
// 组装数据
const element = createASTElement(tagName, attrs);
// 如果root没有值说明这一项是根元素
if(!root) {
root = element;
}
// => 记录当前的 "元素"
currentParent = element;
stack.push(element);//添加到数组里面去 [div] -> [div, span]
}
function end(tagName) {
// span
const element = stack.pop();
// div
currentParent = stack[stack.length - 1];
if(currentParent) {
// span -> parent -> div
element.parent = currentParent;
// div -> children -> span
currentParent.children.push(element);
}
}
// 处理文本字符串
function chars(text) {
// 文本去掉空格
text = text.trim();
if(text.length > 0) {
// 当前元素添加文本
currentParent.children.push({
type: 3,
text
})
}
}
// 元素类型的
function createASTElement(tagName, attrs) {
return {
tag: tagName,
type: 1,
attrs,
children: [],
parent
}
}
console.log(root);
return root;
}
export {
parseHtmlToAst
}
- 处理的AST树数据格式如下: