解析标签和内容
优先级(render函数 > template > el)
TS
JavaScript
const mount = Vue.prototype.$mount
// 函数劫持 aop 切片,增加将模板进行编译的逻辑
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
// 如果没有 render 方法
if (!options.render) {
let template = options.template;
// 如果没有模板但是有 el
if (!template && el) {
template = el.outerHTML;
}
// 将模板转化成了 render 函数
const render = compileToFunctions(template);
options.render = render;
}
return mount.call(this, el, hydrating)
}
export function compileToFunctions(template) {
parseHTML(template);
return function () { }
}
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
function parseHTML(html) {
while (html) {
let textEnd = html.indexOf('<');
if (textEnd == 0) {
const startTagMatch = parseStartTag();
if (startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
const endTagMatch = html.match(endTag);
if (endTagMatch) {
advance(endTagMatch[0].length);
end(endTagMatch[1]);
continue;
}
}
let text;
if (textEnd >= 0) {
text = html.substring(0, textEnd);
}
if (text) {
advance(text.length);
chars(text);
}
}
function advance(n) {
html = html.substring(n);
}
function parseStartTag() {
const start = html.match(startTagOpen);
if (start) {
const match = {
tagName: start[1],
attrs: []
}
advance(start[0].length);
let attr, end;
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
advance(attr[0].length);
match.attrs.push({ name: attr[1], value: attr[3] });
}
if (end) {
advance(end[0].length);
return match
}
}
}
}
生成 ast 语法树
JavaScript
JavaScript
// 语法树就是用对象描述 js 语法
{
tag:'div',
type:1,
children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],
attrs:[{name:'zf',age:10}],
parent:null
}
let root;
let currentParent;
let stack = [];
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
// 生成 ast 语法树
function createASTElement(tagName, attrs) {
return {
tag: tagName,
type: ELEMENT_TYPE,
children: [],
attrs,
parent: null
}
}
function start(tagName, attrs) {
let element = createASTElement(tagName, attrs);
if (!root) {
root = element;
}
currentParent = element;
stack.push(element);
}
function end(tagName) {
let element = stack.pop();
currentParent = stack[stack.length - 1];
if (currentParent) {
element.parent = currentParent;
currentParent.children.push(element);
}
}
function chars(text) {
text = text.replace(/\s/g, '');
if (text) {
currentParent.children.push({
type: TEXT_TYPE,
text
})
}
}
生成代码
HTML
JavaScript
JavaScript
<div style="color:red">hello {{ name }} <span></span></div>
// template 转化成 render 函数的结果
render(){
return _c('div', { style: { color: 'red' } }, _v('hello' + _s(name)), _c('span', undefined, ''))
}
// 实现代码生成
function gen(node) {
if (node.type == 1) {
return generate(node);
} else {
let text = node.text
if (!defaultTagRE.test(text)) {
return `_v(${JSON.stringify(text)})`
}
let lastIndex = defaultTagRE.lastIndex = 0
let tokens = [];
let match, index;
while (match = defaultTagRE.exec(text)) {
index = match.index;
if (index > lastIndex) {
tokens.push(JSON.stringify(text.slice(lastIndex, index)));
}
tokens.push(`_s(${match[1].trim()})`)
lastIndex = index + match[0].length;
}
if (lastIndex < text.length) {
tokens.push(JSON.stringify(text.slice(lastIndex)))
}
return `_v(${tokens.join('+')})`;
}
}
function getChildren(el) { // 生成儿子节点
const children = el.children;
if (children) {
return `${children.map(c => gen(c)).join(',')}`
} else {
return false;
}
}
function genProps(attrs) { // 生成属性
let str = '';
for (let i = 0; i < attrs.length; i++) {
let attr = attrs[i];
if (attr.name === 'style') {
let obj = {}
attr.value.split(';').forEach(item => {
let [key, value] = item.split(':');
obj[key] = value;
})
attr.value = obj;
}
str += `${attr.name}:${JSON.stringify(attr.value)},`;
}
return `{${str.slice(0, -1)}}`;
}
function generate(el) {
let children = getChildren(el);
let code = `_c('${el.tag}',${el.attrs.length ? `${genProps(el.attrs)}` : 'undefined'
}${children ? `,${children}` : ''
})`;
return code;
}
let code = generate(root);
生成 render 函数
export function compileToFunctions(template) {
parseHTML(template);
let code = generate(root);
let render = `with(this){return ${code}}`;
let renderFn = new Function(render);
return renderFn
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
剖析全局 VUE 的初始化过程
上一篇
评论