好家伙,
Vue源码学习(三):渲染第二步,创建ast语法树,
在上一篇,我们已经成功将
我们的模板
转换为ast语法树
接下来我们继续进行操作
1.方法封装
由于代码太多,为了增加代码的可阅读性
我们先将代码进行封装
index.js
import { generate } from "./generate"
import { parseHTML } from "./parseAst"
export function compileToFunction(el) {
//1. 将html元素变为ast语法树
let ast = parseHTML(el)
//2. ast语法树变成render函数
//(1) ast语法树变成字符串
//(2) 字符串变成函数
let code = generate(ast) // _c _v _s
console.log(code)
//3.将render字符串变成函数
let render = new Function(`with(this){return ${code}}`)
console.log(render,'this is render')
return render
}
今天我们将注意力集中在generate方法,
ast参数长什么样子?
2.渲染函数generate()作用
首先我们确认,这个generare()方法是干什么的?
将ast语法树变成一个渲染函数
于是,我们来思考几个问题–
问一:为什么要使用generare()方法将ast语法树转换为渲染函数?
答一:在上图我们看见了,这个所谓的语法树其实更像是一个json而不是一个js,
而我们要将模板转换为可执行的JavaScript代码,才能跑
答一(官方一点的版本):
首先,将AST转换为渲染函数可以消除模板的解析和编译的开销。
在Vue的运行时版本中,没有编译器,所以将模板转换为渲染函数能够提高运行时的性能。
其次,渲染函数可以更高效地处理动态渲染。由于AST在编译时已经进行了一些静态分析,因此在渲染函数中可以更好地服务器托管网优化动态渲染和响应式更新的逻辑,减少运行时的开销。
最后,渲染函数的生成也是为了更好地支持vue组件的复用。
通过将模板转换为渲染函数,Vue可以更方便地缓存和复用这些函数,进一步提高服务器托管网组件的渲染性能。
总而言之,使用generate()
方法将AST转换为渲染函数是为了提高Vue应用的性能和效率,优化动态渲染和支持组件的复用。
3.generate()方法得到结果
我们想象一下
let code = generate(ast)
code会是什么样子的?
如果我们去翻源码,大概可以看到这步的结果长这样
然而实际上,我们的四步走: 模板解析 =》AST =》生成渲染函数 =》渲染到真实DOM
渲染函数的下一步是渲染到真实DOM
也就是要预留一个标记给”渲染到真实DOM”这一步做处理
_c 标签
_v 文本
_s 符号
4.generate.js代码解析
3.1.generate()
函数是入口函数,接受一个 AST 语法书作为参数:
export function generate(el) {
console.log(el,'|this is el')
let children = genChildren(el)
console.log(children, "|this is children")
let code = `_c('${el.tag}',${el.attrs.length?`${genPorps(el.attrs)}`:'undefined'},${
children?`${children}`:''
})`
console.log(code, '|this is code')
return code
}
- (1) 调用
genChildren()
函数获取子节点代码字符串。 - (2) 拼接元素节点的标签名( genPoros()方法 )、属性和子节点代码,并返回生成的渲染函数代码。
generate.js完整代码
/** *Hello{{msg}}* * _c 解析标签 * _v 解析字符串 * * render(){ * return _c('div',{id:app},_v('hello'+_s(msg)),_c) * } * */ //处理属性 const defaultTagRE = /{{((?:.|r?n)+?)}}/g //genPorps()方法解析属性 function genPorps(attrs) { // console.log(attrs) let str = ''; //对象 for (let i = 0; i ) { let attr = attrs[i] if (attr.name === 'style') { // let obj = {} attr.value.split(';').forEach(item => { let [key, val] = item.split(':') // console.log(key, val, "//this is [key,val]") obj[key] = val }) attr.value = obj } //拼接 str += `${attr.name}:${JSON.stringify(attr.value)},` // console.log(str, '|this is str') // console.log(`{${str.slice(0,-1)}}`) } //首字符到倒数第二个字符,即去掉标点符号 return `{${str.slice(0,-1)}}` } //处理子节点 function genChildren(el) { let children = el.children //获取元素节点的子节点 //如果存在子节点,则递归调用 gen() 函数处理每个子节点,并用逗号拼接子节点的代码。 if (children) { //返回子节点代码的字符串。 return children.map(child => gen(child)).join(',') } } // function gen(node) { //1.元素 2.div tip:_v表示文本 // console.log(node, "this is node") //如果节点是元素节点,递归调用 generate() 函数处理该节点,并返回结果。 if (node.type === 1) { return generate(node) } else { //文本 //(1) 只是文本 hello (2){{}} let text = node.text //获取文本 //转化 if (!defaultTagRE.test(text)) { return `_v(${JSON.stringify(text)})` } //(2)带插值表达式{{}} //文本包含插值表达式,使用正则表达式 defaultTagRE //查找所有 {{}} 形式的插值表达式,并解析成可执行的代码片段。 let tokens = [] //lastIndex 需要清零 否则test匹配会失败 let lastindex = defaultTagRE.lastIndex = 0 //match保存获取结果 let match while (match = defaultTagRE.exec(text)) { console.log(match, "|this is match") let index = match.index if (index > lastindex) { tokens.push(JSON.stringify(text.slice(lastindex, index))) //内容 } tokens.push(`_s(${match[1].trim()})`) //lastindex处理文本长度 lastindex = index + match[0].length } //此处if用于处理`Hello{{msg}} xxx`中的xxx if (lastindex text.slice(lastindex)) { tokens.push(JSON.stringify(text.slice(lastindex, index))) //内容 } return `_v(${tokens.join('+')})` } } export function generate(el) { console.log(el,'|this is el') let children = genChildren(el) console.log(children, "|this is children") let code = `_c('${el.tag}',${el.attrs.length?`${genPorps(el.attrs)}`:'undefined'},${ children?`${children}`:'' })` console.log(code, '|this is code') return code }
(代码注释已十分完善)
方法解释(简化版本):
3.2.genChildren()
函数 处理子节点
- 获取元素节点的子节点。
- 如果存在子节点,则递归调用
gen()
函数处理每个子节点,并用逗号拼接子节点的代码。 - 返回子节点代码的字符串。
3.3.gen()
函数 根据节点类型生成代码:
- 如果节点是元素节点,递归调用
generate()
函数处理该节点,并返回结果。 -
如果节点是文本节点:返回处理后的节点代码。
- 如果文本不包含插值表达式
{{}}
,则使用_v()
方法将文本转换为可执行的字符串形式。 - 如果文本包含插值表达式,使用正则表达式
defaultTagRE
查找所有{{}}
形式的插值表达式,并解析成可执行的代码片段。
- 如果文本不包含插值表达式
- 返回处理后的节点代码。
3.4.genProps()
函数 解析属性:
- 遍历元素节点的所有属性,拼接属性名和属性值。
- 如果属性名是
"style"
,则将属性值解析为对象形式。 - 返回拼接后的属性字符串。
5.render字符串变成函数
上述操作结束后,我们得到还是字符串,现在我们将其变成一个函数
let render = new Function(`with(this){return ${code}}`)
这里为什么要用with(this) ??
答:而with(this)
是将当前组件实例(即Vue组件的this
)作为上下文绑定到函数中,这样在渲染函数中就可以访问组件实例的属性和方法,例如访问组件的数据、计算属性或方法。
调试部分以及最终结果输出
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
以下是一个C++ QT操作MySQL数据库的工具类示例代码,包括连接数据库、创建表、插入数据、删除数据、修改数据、查询数据、查询表字段名称等操作: #include #include #include #in…