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