vue几种编译_vue-模板编译

在创建vue实例时,会传入el或者template模板,在vue内部会将模板编译成一个渲染函数render。模板编译的过程如下:

compileToFunction将模板转为render函数

function compileToFunction(template) {

let root = parseHTML(template)

let code = generate(root)

let renderFn = new Function(`with(this){return ${code}}`)

return renderFn

}

复制代码

生成AST语法树

AST语法树,是源代码抽象语法结构的树状表现形式。树上的每个节点都表示源代码中的一种结构。

parseHTML

const ncname = '[a-zA-Z_][\\w\\-\\.]*'; // 匹配abc-aaa

const qnameCapture = `((?:${ncname}\\:)?${ncname})`; //

const startTagOpen = new RegExp(`^

const endTag = new RegExp(`^]*>`);// 匹配标签结尾

const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;// 匹配属性

const startTagClose = /^\s*(\/?)>/;// 匹配标签结束的>

let root = null; // ast的树根

let curParent; // 标识当前父级

let stack = [];

const ELEMENT_TYPE = 1

const TEXT_TYPE = 3;

function parseHTML(html) {

while(html) { // 循环匹配

let textEnd = html.indexOf('

if(textEnd == 0) { // 如果是开头 可以匹配或者 xx>

let startTagMatch = parseStartTag(); // 1. 匹配

if (startTagMatch) {

start(startTagMatch.tagName, startTagMatch.attrs);

continue;

}

let endTagMatch = html.match(endTag) // 2.匹配

if (endTagMatch) {

advance(endTagMatch[0].length);

end(endTagMatch[1]);

continue;

}

}

let text;

if (textEnd >= 0) { // 3.匹配文本

text = html.substring(0, textEnd)

}

if (text) {

advance(text.length);

chars(text)

}

}

// 从html上删除字符串

function advance(n) {

html = html.substring(n)

}

// 解析开始标签

function parseStartTag() {

let start = html.match(startTagOpen); // 匹配

if(start){

const match = {

tagName: start[1],

attrs: []

}

advance(start[0].length);

let end, attr;

while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { // 匹配m=n

advance(attr[0].length);

match.attrs.push({

name: attr[1],

value: attr[3] || attr[4] || attr[5]

})

}

if (end) { // 匹配>

advance(end[0].length);

return match

}

}

}

return root

}

复制代码

start + createASTElement处理tagName和attrs

// 创建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

}

curParent = element;

stack.push(element)

}

复制代码

chars处理文本

function chars(text){

text = text.replace(/\s/g, '');

if(text) {

curParent.children.push({

text,

type: TEXT_TYPE

})

}

}

复制代码

end处理父子关系

定义全局变量stack和cuParent;

当start时,保存curParent = element和stack.push(element)

当end时,通过pop获取stack最后一项,它为element;它的父级为新stack的最后一项;建立双向父子关系

function end(tagName) {

let element = stack.pop();

curParent = stack[stack.length - 1]

if (curParent) {

element.parent = curParent;

curParent.children.push(element)

}

}

复制代码

将AST语法树generate为模板code--模板引擎

转为render函数是一个字符串拼接的过程。如果是元素,用_c包裹;如果是文本,用_v包裹;如果是变量,用_s包裹

generate:将AST转为模板字符串

function generate(el){

const {tag, attrs, children} = el

let newChildren = genChildren(el)

// _c创建元素

let code = `_c('${tag}', ${

attrs.length > 0 ? genProps(attrs) : 'undefined'

}, ${

newChildren ? newChildren : ''

})`;

console.log(code, 'code')

return code

}

复制代码

genProps:将属性数组转为属性子符串

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 = JSON.stringify(obj);

str += `${attr.name}:${attr.value},`

} else {

str += `${attr.name}:'${attr.value}',`

}

}

return `{${str.slice(0, -1)}}`

}

复制代码

genChildren:将children转为字符串children

function genChildren(el) {

let {children} = el;

if(children && children.length) {

return `${children.map(c => genC(c)).join(',')}`

} else {

return false

}

}

复制代码

genC:根据child类型不同,进行不同转译

const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // {{任意字符|换行}}

function genC(node) {

if (node.type === 1) { //元素时,递归调用genenrate

return generate(node)

} else if (node.type === 3) {//文本时,区分普通文本和变量

let text = node.text;

let tokens = [];

let match, index;

let lastIndex = defaultTagRE.lastIndex = 0;

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('+')})`

}

}

复制代码

生成render

通过with改变作用域

通过newFunction生成新的function

let renderFn = new Function(`with(this){return ${code}}`)

复制代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值