頁面元素轉(zhuǎn)換成ast語法樹

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)
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姿骏,一起剝皮案震驚了整個濱河市糖声,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌分瘦,老刑警劉巖蘸泻,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘲玫,居然都是意外死亡蟋恬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門趁冈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歼争,“玉大人拜马,你說我怎么就攤上這事°迦蓿” “怎么了俩莽?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乔遮。 經(jīng)常有香客問我扮超,道長,這世上最難降的妖魔是什么蹋肮? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任出刷,我火速辦了婚禮,結(jié)果婚禮上坯辩,老公的妹妹穿的比我還像新娘馁龟。我一直安慰自己,他們只是感情好漆魔,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布坷檩。 她就那樣靜靜地躺著,像睡著了一般改抡。 火紅的嫁衣襯著肌膚如雪矢炼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天阿纤,我揣著相機(jī)與錄音句灌,去河邊找鬼。 笑死欠拾,一個胖子當(dāng)著我的面吹牛涯塔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播清蚀,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼匕荸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枷邪?” 一聲冷哼從身側(cè)響起榛搔,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎东揣,沒想到半個月后践惑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡嘶卧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年尔觉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芥吟。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡侦铜,死狀恐怖专甩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钉稍,我是刑警寧澤涤躲,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站贡未,受9級特大地震影響种樱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俊卤,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一嫩挤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧消恍,春花似錦岂昭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叼风。三九已至取董,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間无宿,已是汗流浹背茵汰。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留孽鸡,地道東北人蹂午。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像彬碱,于是被迫代替她去往敵國和親豆胸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容