好家伙,书接上回
在上一篇Vue源码学习(二):<templete>渲染第一步,模板解析中,我们完成了模板解析
现在我们继续,将模板解析的转换为ast语法树
代码已开源https://github.com/Fattiger4399/analytic-vue.git手动调试一遍,
胜过我解释给你听一万遍
function start(tag, attrs) { //开始标签 console.log(tag, attrs, '开始的标签') } function charts(text) { //获取文本 console.log(text, '文本') } function end(tag) { //结束的标签 console.log(tag, '结束标签') }复制
在这里,我们知道start,charts,end分别可以拿到
我们的`开始标签`,`文本`,`结束标签`
效果如下:(仔细看,这也是我们实验要用到的例子)
随后我们开始改造这几个方法
确定我们ast树节点的结构:
let root; //根元素 let createParent //当前元素的父亲 let stack = [] function createASTElement(tag, attrs) { return { tag, attrs, children: [], type: 1, parent: null } }复制
节点元素分别为
function start(tag, attrs) { //开始标签 let element = createASTElement(tag, attrs) //生成一个开始标签元素 //查看root根元素是否为空 //若是,将该元素作为根 //非原则 if (!root) { root = element } createParent = element stack.push(element) console.log(tag, attrs, '开始的标签') }复制
此处,生成一个开始标签元素,判断root是否为空,若为空,则将该元素作为根元素
随后将该元素作为父元素.
function charts(text) { //获取文本 console.log(text, '文本') // text = text.replace(/a/g,'') if(text){ createParent.children.push({ type:3, text }) } // console.log(stack,'stack') }复制
这个好理解,将"文本内容"作为父元素的孩子
function end(tag) { //结束的标签 let element = stack.pop() createParent = stack[stack.length - 1] if (createParent) { //元素闭合 element.parent = createParent.tag createParent.children.push(element) } console.log(tag, '结束标签') }复制
此处,我们先将栈stack最新的元素弹出栈(作为当前元素,我们要对他进行操作),
随后获取栈的前一个元素作为父元素,
当前元素的父元素属性指向父元素的标签属性
随后将该元素推入父元素的children中,
emmmm,我还是说人话吧
假设现在stack=['div','h1']
然后pop了,createParent = 'h1'
'h1'.parent =>'div'
'div'.children =>'h1'
(多看几遍就理解了,其实非常简单)
来看看最终实现的ast语法树长什么样子
(父子关系和谐)
搞定啦!
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ //属性 例如: {id=app} const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; //标签名称 const qnameCapture = `((?:${ncname}\\:)?${ncname})` //<span:xx> const startTagOpen = new RegExp(`^<${qnameCapture}`) //标签开头 const startTagClose = /^\s*(\/?)>/ //匹配结束标签 的 > const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) //结束标签 例如</div> const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g let root; //根元素 let createParent //当前元素的父亲 let stack = [] function createASTElement(tag, attrs) { return { tag, attrs, children: [], type: 1, parent: null } } function start(tag, attrs) { //开始标签 let element = createASTElement(tag, attrs) //生成一个开始标签元素 //查看root根元素是否为空 //若是,将该元素作为根 //非原则 if (!root) { root = element } createParent = element stack.push(element) console.log(tag, attrs, '开始的标签') } function charts(text) { //获取文本 console.log(text, '文本') // text = text.replace(/a/g,'') if(text){ createParent.children.push({ type:3, text }) } // console.log(stack,'stack') } function end(tag) { //结束的标签 let element = stack.pop() createParent = stack[stack.length - 1] if (createParent) { //元素闭合 element.parent = createParent.tag createParent.children.push(element) } console.log(tag, '结束标签') } export function parseHTML(html) { while (html) { //html 为空时,结束 //判断标签 <> let textEnd = html.indexOf('<') //0 // console.log(html,textEnd,'this is textEnd') if (textEnd === 0) { //标签 // (1) 开始标签 const startTagMatch = parseStartTag() //开始标签的内容{} if (startTagMatch) { start(startTagMatch.tagName, startTagMatch.attrs); continue; } // console.log(endTagMatch, '结束标签') //结束标签 let endTagMatch = html.match(endTag) if (endTagMatch) { advance(endTagMatch[0].length) end(endTagMatch[1]) continue; } } let text //文本 if (textEnd > 0) { // console.log(textEnd) //获取文本内容 text = html.substring(0, textEnd) // console.log(text) } if (text) { advance(text.length) charts(text) // console.log(html) } } function parseStartTag() { // const start = html.match(startTagOpen) // 1结果 2false // console.log(start,'this is start') // match() 方法检索字符串与正则表达式进行匹配的结果 // console.log(start) //创建ast 语法树 if (start) { let match = { tagName: start[1], attrs: [] } // console.log(match,'match match') //删除 开始标签 advance(start[0].length) //属性 //注意 多个 遍历 //注意> let attr //属性 let end //结束标签 //attr=html.match(attribute)用于匹配 //非结束位'>',且有属性存在 while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { // console.log(attr,'attr attr'); //{} // console.log(end,'end end') match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] }) advance(attr[0].length) //匹配完后,就进行删除操作 } //end里面有东西了(只能是有">"),那么将其删除 if (end) { // console.log(end) advance(end[0].length) return match } } } function advance(n) { // console.log(html) // console.log(n) html = html.substring(n) // substring() 方法返回一个字符串在开始索引到结束索引之间的一个子集, // 或从开始索引直到字符串的末尾的一个子集。 // console.log(html) } // console.log(root) return root }复制
好家伙, 1.使用场景 现在来,来想一下,作为一个使用Vue的开发者,假设现在我们要使用created(),我们会如何使用 1.1. .vue文件中使用 {{ message }}