vue parseHTML函數(shù)解析器遇到結(jié)束標(biāo)簽,在之前文章中已講述完畢。
例如有html(template)字符串:
<div id="app"> <p>{{ message }}</p> </div>
產(chǎn)出如下:
{ attrs: [" id="app"", "id", "=", "app", undefined, undefined] end: 14 start: 0 tagName: "div" unarySlash: "" } { attrs: [] end: 21 start: 18 tagName: "p" unarySlash: "" }
上面就是寫明AST(抽象語法樹)??
但答案是:No 這個并非是我們想要的AST,parse 階段最終成為的樹形態(tài)應(yīng)該是與如上html(template)字符串的結(jié)構(gòu)一一對應(yīng)的:
├── div │ ├── p │ │ ├── 文本
如果每一個節(jié)點(diǎn)我們都用一個 javascript 對象來表示的話,那么 div 標(biāo)簽可以表示為如下對象:
{ type: 1, tag: "div" }
子節(jié)點(diǎn)
節(jié)點(diǎn)中都包含有一個一個父節(jié)點(diǎn)和若干子節(jié)點(diǎn),需要添加兩個對象屬性:parent 和 children ,分別用來表示當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)和它所包含的子節(jié)點(diǎn):
{ type: 1, tag:"div", parent: null, children: [] }
同時每個元素節(jié)點(diǎn)還可能包含很多屬性 (attributes),但每個節(jié)點(diǎn)任然要添加attrsList屬性,是為了用來存儲當(dāng)前節(jié)點(diǎn)所擁有的屬性:
{ type: 1, tag:"div", parent: null, children: [], attrsList: [] }
按照以上思路去描述之前定義的 html 字符串,那么這棵抽象語法樹應(yīng)該長成如下這個樣子:
{ type: 1, tag: "div", parent: null, attrsList: [], children: [{ type: 1, tag: "p", parent: div, attrsList: [], children:[ { type: 3, tag:"", parent: p, attrsList: [], text:"{{ message }}" } ] }], }
我們現(xiàn)在的說是就要建立一個能夠類似如上所示的一個能夠描述節(jié)點(diǎn)關(guān)系的對象樹,讓節(jié)點(diǎn)與節(jié)點(diǎn)之間通過 parent 和 children 建立聯(lián)系,這樣就可以實(shí)現(xiàn)每個節(jié)點(diǎn)的 type 屬性用來標(biāo)識該節(jié)點(diǎn)的類別。
這里可參考NodeType:https://www.w3school.com.cn/jsref/prop_node_nodetype.asp
現(xiàn)在我們總結(jié)所學(xué) parseHTML 函數(shù),只是在生成 AST 中的一個重要環(huán)節(jié)并非全部。 那在Vue中是如何把html(template)字符串編譯解析成AST的呢?
Vue中是如何把html(template)字符串編譯解析成AST
在源碼中:
function parse (html) { var root; parseHTML(html, { start: function (tag, attrs, unary) { // 省略... }, end: function (){ // 省略... } }) return root }
接下來重點(diǎn)就來看看他們做了什么。parse函數(shù)返回root,其中root 所代表的就是整個模板解析過后的 AST,現(xiàn)在我們要用到另兩個重要的鉤子函數(shù):options.start 、options.end。
下面進(jìn)入Vue在進(jìn)行模板編譯詞法分析階段調(diào)用了parse函數(shù),
解析html
假設(shè)解析的html字符串如下:
<div></div>
這是一個沒有任何子節(jié)點(diǎn)的div 標(biāo)簽。如果要解析它,我們來簡單寫下代碼。
function parse (html) { var root; parseHTML(html, { start: function (tag, attrs, unary) { var element = { type: 1, tag: tag, parent: null, attrsList: attrs, children: [] } if (!root) root = element }, end: function (){ // 省略... } }) return root }
如上: 在start 鉤子函數(shù)中首先定義了 element 變量,它就是元素節(jié)點(diǎn)的描述對象,接著判斷root 是否存在,如果不存在則直接將 element 賦值給 root 。當(dāng)解析這段 html 字符串時首先會遇到 div 元素的開始標(biāo)簽,此時 start 鉤子函數(shù)將被調(diào)用,最終 root 變量將被設(shè)置為:
{ type: 1, tag:"div", parent: null, children: [], attrsList: [] }
html 字符串復(fù)雜度升級: 比之前的 div 標(biāo)簽多了一個子節(jié)點(diǎn),span 標(biāo)簽。
<div> <span></span> </div>
代碼重新改造
此時需要把代碼重新改造。
function parse (html) { var root; var currentParent; parseHTML(html, { start: function (tag, attrs, unary) { var element = { type: 1, tag: tag, parent: null, attrsList: attrs, children: [] } if (!root){ root = element; }else if(currentParent){ currentParent.children.push(element) } if (!unary) currentParent = element }, end: function (){ // 省略... } }) return root }
我們知道當(dāng)解析如上 html 字符串時首先會遇到 div 元素的開始標(biāo)簽,此時 start 鉤子函數(shù)被調(diào)用,root變量被設(shè)置為:
{ type: 1, tag:"div", parent: null, children: [], attrsList: [] }
還沒完可以看到在 start 鉤子函數(shù)的末尾有一個 if 條件語句,當(dāng)一個元素為非一元標(biāo)簽時,會設(shè)置 currentParent 為該元素的描述對象,所以此時currentParent也是:
{ type: 1, tag:"div", parent: null, children: [], attrsList: [] }
接著解析 html (template)字符串
接著解析 html (template)字符串,會遇到 span 元素的開始標(biāo)簽,此時root已經(jīng)存在,currentParent 也存在,所以會將 span 元素的描述對象添加到 currentParent 的 children 數(shù)組中作為子節(jié)點(diǎn),所以最終生成的 root 描述對象為:
{ type: 1, tag:"div", parent: null, attrsList: [] children: [{ type: 1, tag:"span", parent: div, attrsList: [], children:[] }], }
到目前為止好像沒有問題,但是當(dāng)html(template)字符串復(fù)雜度在升級,問題就體現(xiàn)出來了。
<div> <span></span> <p></p> </div>
在之前的基礎(chǔ)上 div 元素的子節(jié)點(diǎn)多了一個 p 標(biāo)簽,到解析span標(biāo)簽的邏輯都是一樣的,但是解析 p 標(biāo)簽時候就有問題了。
注意這個代碼:
if (!unary) currentParent = element
在解析 p 元素的開始標(biāo)簽時,由于 currentParent 變量引用的是 span 元素的描述對象,所以p 元素的描述對象將被添加到 span 元素描述對象的 children 數(shù)組中,被誤認(rèn)為是 span 元素的子節(jié)點(diǎn)。而事實(shí)上 p 標(biāo)簽是 div 元素的子節(jié)點(diǎn),這就是問題所在。
為了解決這個問題,就需要我們額外設(shè)計一個回退的操作,這個回退的操作就在end鉤子函數(shù)里面實(shí)現(xiàn)。
解析div
這是一個什么思路呢?舉個例子在解析div 的開始標(biāo)簽時:
stack = [{tag:"div"...}]
在解析span 的開始標(biāo)簽時:
stack = [{tag:"div"...},{tag:"span"...}]
在解析span 的結(jié)束標(biāo)簽時:
stack = [{tag:"div"...}]
在解析p 的開始標(biāo)簽時:
stack = [{tag:"div"...},{tag:"p"...}]
在解析p 的標(biāo)簽時:
這個退回操作就能保證在解析p開始標(biāo)簽的時候,stack中存儲的是p標(biāo)簽父級元素的描述對象。
接下來繼續(xù)改造我們的代碼。
function parse (html) { var root; var currentParent; var stack = []; parseHTML(html, { start: function (tag, attrs, unary) { var element = { type: 1, tag: tag, parent: null, attrsList: attrs, children: [] } if (!root){ root = element; }else if(currentParent){ currentParent.children.push(element) } if (!unary){ currentParent = element; stack.push(currentParent); } }, end: function (){ stack.pop(); currentParent = stack[stack.length - 1] } }) return root }
上述代碼主要是為實(shí)現(xiàn),在遇見非一元標(biāo)簽的結(jié)束標(biāo)簽時,這樣就會退回currentParent 變量的值為之前的值,這樣我們就修正了當(dāng)前正在解析的元素的父級元素。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/127819.html
寫文章不容易,點(diǎn)個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號也可以吧 【Vue原理】Compile - 源碼版 之 Parse 主要流程 本文難度較繁瑣,需要耐心觀看,如果你對 compile 源碼暫時...
摘要:下面用具體代碼進(jìn)行分析。匹配不到那么就是開始標(biāo)簽,調(diào)用函數(shù)解析。如這里的轉(zhuǎn)化為加上是為了的下一步轉(zhuǎn)為函數(shù),本文中暫時不會用到。再把轉(zhuǎn)化后的內(nèi)容進(jìn)。 什么是AST 在Vue的mount過程中,template會被編譯成AST語法樹,AST是指抽象語法樹(abstract syntax tree或者縮寫為AST),或者語法樹(syntax tree),是源代碼的抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式。...
直接進(jìn)入核心現(xiàn)在說說baseCompile核心代碼: //`createCompilerCreator`allowscreatingcompilersthatusealternative //parser/optimizer/codegen,e.gtheSSRoptimizingcompiler. //Herewejustexportadefaultcompilerusingthede...
在說Vue parse源碼之前,首先要了解周邊的工具函數(shù)。 之前見過element元素節(jié)點(diǎn)四描述對象? varelement={ type:1, tag:tag, parent:null, attrsList:attrs, children:[] } 是用一個createASTElement函數(shù),創(chuàng)建函數(shù)對象?! reateASTElement函數(shù) funct...
摘要:模板解析器原理本文來自深入淺出模板編譯原理篇的第九章,主要講述了如何將模板解析成,這一章的內(nèi)容是全書最復(fù)雜且燒腦的章節(jié)。循環(huán)模板的偽代碼如下截取模板字符串并觸發(fā)鉤子函數(shù)為了方便理解,我們手動模擬解析器的解析過程。 Vue.js 模板解析器原理 本文來自《深入淺出Vue.js》模板編譯原理篇的第九章,主要講述了如何將模板解析成AST,這一章的內(nèi)容是全書最復(fù)雜且燒腦的章節(jié)。本文未經(jīng)排版,真...
閱讀 687·2023-03-27 18:33
閱讀 889·2023-03-26 17:27
閱讀 755·2023-03-26 17:14
閱讀 738·2023-03-17 21:13
閱讀 667·2023-03-17 08:28
閱讀 2092·2023-02-27 22:32
閱讀 1521·2023-02-27 22:27
閱讀 2432·2023-01-20 08:28