[Vue源码] Vue中模板编译原理

一、Vue中模板编译原理

原理:将 template 转化成 render 函数

抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。

Vue2.0用的是 simplehtmlparser.js by JQ之父 Erik Arvidsson

1、将模板转化成AST抽象语法树
function baseCompile ( template: string, options: CompilerOptions ) {
	const ast = parse(template.trim(), options) // 1.将模板转化成ast语法树 
	if (options.optimize !== false) { // 2.优化树 
		optimize(ast, options) 
	}
	const code = generate(ast, options) // 3.生成树 
	return { 
		ast, 
		render: code.render, 
		staticRenderFns: code.staticRenderFns 
	} 
})
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*(\/?)>/; // 匹配标签结束的 > 
let root; 
let currentParent; 
let stack = [] 

function createASTElement(tagName,attrs){ 
	return { 
		tag:tagName, 
		type:1, 
		children:[], 
		attrs, 
		parent:null 
	}
}
function start(tagName,attrs){ 
	let element = createASTElement(tagName,attrs); 
	if(!root){ 
		root = element; 
	}
	currentParent = element; 
	stack.push(element); 
}
function chars(text){ 
	currentParent.children.push({ type:3, text }) 
}
function end(tagName){ 
	const element = stack[stack.length-1]; 
	stack.length --; 
	currentParent = stack[stack.length-1];
	if(currentParent){ 
		element.parent = currentParent; 
		currentParent.children.push(element) 
		} 
}
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]) 
			} 
		}
		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 
			} 
		} 
	} 
}

// 生成语法树

parseHTML(`<div id="container"><p>hello<span>zf</span></p></div>`); 

function gen(node){ 
	if(node.type == 1){ 
		return generate(node); 
	}else{
		return `_v(${JSON.stringify(node.text)})` 
	} 
}

function genChildren(el){ 
	const children = el.children; 
	if(el.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]; 
		str+= `${attr.name}:${attr.value},`; 
	}
	return `{attrs:{${str.slice(0,-1)}}}` 
}
function generate(el){ 
	let children = genChildren(el); 
	let code = `_c('${el.tag}'${ 
		el.attrs.length? `,${genProps(el.attrs)}`:'' 
	}${ 
		children? `,${children}`:'' 
	})`; 
	return code; 
}
2、 根据语法树生成新的代码 render函数
let code = generate(root); 
let render = `with(this){ return ${code}}`; 
3、包装成函数
let renderFn = new Function(render); 
console.log(renderFn.toString());
4、最后在生成虚拟Dom

模板编译过程:

template =>ast树=>codegen方法 =>转成render函数=>内部调用_c方法=>虚拟Dom

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值