html元素轉(zhuǎn)換成ast語法樹
=>html元素:
<div id='app' style="color:red">hello {{msg}}</div>
=>轉(zhuǎn)換成ast語法樹:
{"tag":"div","type":1,"children":[{"type":3,"text":"hello{{msg}}"}],"attrs":[{"name":"id","value":"app"},{"name":"style","value":"color:red"}],"parent":null}
/**ast語法樹格式
* {
* tag:'div',
* attrs:[{id:'app'},style:{style:'color: "red"'}]
* children:[{tag:null,text:'hello'},{tag:'h1',text:'world'}]
* }
*/
源碼里匹配正則表達(dá)式
// 標(biāo)簽名稱
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // 小a-z 大A到Z 標(biāo)簽名稱: div span a-aa
//?: 匹配不捕獲
// 特殊標(biāo)簽名稱 <span:xx>
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; // 捕獲這種 <my:xx> </my:xx>
// tag標(biāo)簽開頭
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 標(biāo)簽開頭的正則 捕獲的內(nèi)容是標(biāo)簽名
// tag標(biāo)簽結(jié)尾
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配標(biāo)簽結(jié)尾的 </div>
// 匹配屬性
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配屬性的
//屬性匹配 <div id="atts"></div> // aa = "aa" | aa = 'aa'
// 匹配結(jié)束標(biāo)簽
const startTagClose = /^\s*(\/?)>/; // 匹配標(biāo)簽結(jié)束的 <div></div> <br/>
// 匹配{{}}
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g // {{xx}} 默認(rèn)的 雙大括號
創(chuàng)建模板編譯方法compileToFunction()
function compileToFunction(template){
let ast = parseHTML(template)
}
在模板編譯方法里創(chuàng)建解析html的方法parseHTML()
<div id='app' style="color:red">{{msg}}<h1>world</h1></div>
// 對html使用遍歷的思想功茴,識別一個元素牵囤,刪除一個元素织阅,知道將html變成空
// 一個標(biāo)簽的三大類:開始標(biāo)簽(<) 文本(id,class,style...) 結(jié)束標(biāo)簽(>)
function parseHTML(html){
while(html){ // 當(dāng)html為空的時候結(jié)束
// 判斷標(biāo)簽
let textEnd = html.indexof('<') // 當(dāng)為0時,可以判斷是標(biāo)簽
if(textEnd === 0){
// 使用正則判斷是開始標(biāo)簽還是結(jié)束標(biāo)簽
const startTagMatch = parseStartTag()
if(startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs)
// 結(jié)束標(biāo)簽
let endTagMatch = html.match(endTag)
if(endTagMatch){
advance(endTagMatch[0].length)
}
continue;
}
}
// 文本
if(textEnd > 0){
//獲取文本內(nèi)容
let text = html.substring(0,textEnd) // 內(nèi)容為:{{msg}}
}
// 判斷是否有文本赋咽,如果有文本,將文本刪除
if(text){
advance(text.length)
charts(text)
}
}
}
解析開始標(biāo)簽的方法parseStartTag()
// 解析開始標(biāo)簽
function parseStartTag(){
// 獲取開始標(biāo)簽
// 返回值為:["<div", "div", index: 0, input: "<div id=\"app\" style=\"color:red\">{{msg}}<h1>world</h1></div>", groups: undefined]
const start = html.match(startTagOpen) // html是當(dāng)前上下文的父級上下文中的html元素
// 創(chuàng)建一個語法樹(初級形態(tài)),定義一個變量match 將返回值存起來
let match = {
tagName: start[1], // 內(nèi)容為"<div"
attrs:[] // 通過處理開始標(biāo)簽之后律想,將對應(yīng)的屬性push進(jìn)來
}
// 刪除已經(jīng)解析過的開始標(biāo)簽 <div
adance(start[0].length) // 傳入html需要刪除的長度
// 刪除之后的html:id="app" style="color:red">{{msg}}<h1>world</h1></div>
// 處理屬性
// warnning:多個屬性逞壁,采用遍歷的思想
// warnning: 注意結(jié)束標(biāo)簽>
let end; // 匹配到的標(biāo)簽結(jié)束標(biāo)記 >
let attr; // 匹配到的標(biāo)簽內(nèi)的屬性
while(!(html.match(startTagClose)&&(attr=html.match(attribute))){ // 利用startTagClose正則匹配流济,判斷是否是結(jié)尾標(biāo)簽 > ,利用attribute正則匹配是否有屬性
// attr= [" id=\"app\"", "id", "=", "app", undefined, undefined, index: 0, input: " id=\"app\" style=\"color:red\">{{msg}}<h1>world</h1></div>", groups: undefined]
match.attrs.push({name:attr[1],value:attr[3]||attr[4]||attr[5]})
advance(attr[0].length) // 刪除html中已經(jīng)處理過的屬性
// 刪除完之后的html= '>{{msg}}<h1>world</h1></div>' ,所以還需要處理開始的 >
}
if(end){
// end = [">", "", index: 0, input: ">{{msg}}<h1>world</h1></div>", groups: undefined]
advance(end[0].length)
return match // 返回語法樹
}
}
刪除已經(jīng)解析過的標(biāo)簽方法adance()
function adance(n){
html.substring(n) // 對html進(jìn)行刪除,并返回新的html
}
創(chuàng)建ast語法樹
<div id='app' style="color:red">{{msg}}<h1>world</h1></div>
// ast語法樹就是一個對象
function createASTElemnet(tag,attrs){
return{
tag, // 表示元素腌闯,div
attrs, // 屬性 id class style
children:[] // 子節(jié)點绳瘟,嵌套的div
type:1, // 元素的類型
parent:null
}
}
判斷有沒有根元素
let root; // 根元素
let createParent // 當(dāng)前元素的父親
// 數(shù)據(jù)結(jié)構(gòu) 棧
let stack = [] // 頁面元素解析完后需要入棧處理
定義開始標(biāo)簽處理方法start()
function start(tag, attrs){ // 開始標(biāo)簽
let element = createASTElement(tag, attrs)
if(!root){ // 如果沒有根元素,則將獲取到的標(biāo)簽設(shè)置為根元素
root = element
}
createParent = element // 賦值父親元素
stack.push(element)
}
定義文本處理方法charts()
function charts(text){ // 文本標(biāo)簽
// 處理空格
text = text.replace(/\s/g,'')
if(text){
createParent.children.push({
type:3, // 文本的類型是3
text
})
}
}
定義結(jié)束標(biāo)簽處理方法end()
function end(tag){ // 結(jié)束標(biāo)簽
let element = stack.pop() // 獲取最后一個元素
createParent = stack[stack.lenth - 1]
if (createParent){ // 元素閉合
element.parent = createParent.tag
createParent.children.push(element)
}
}