<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
承接上篇 parseHTML 函數原始碼解析拿到返回值後的處理
接下來我們將會講解當 textEnd === 0 解析器遇到結束標籤,parse 結束標籤的程式碼如下:
// End tag: var endTagMatch = html.match(endTag); if (endTagMatch) { var curIndex = index; advance(endTagMatch[0].length); parseEndTag(endTagMatch[1], curIndex, index); continue }
首先呼叫 html 字串的 match 函數匹配正則 endTag ,將結果儲存在常數endTagMatch中。正則 endTag 用來匹配結束標籤,並且擁有一個捕獲組用來捕獲標籤名字,比如有如下html 字串:
<div></div>
endTagMatch 輸出如下:
endTagMatch = [
'</div>',
'div'
]
第一個元素是整個匹配到的結束標籤字串
第二個元素是對應的標籤名字。
如果匹配成功 if 語句塊的程式碼將被執行,首先使用 curIndex 常數儲存當前 index 的值,然後呼叫 advance 函數,並以 endTagMatch[0].length 作為引數,接著呼叫了 parseEndTag 函數對結束標籤進行解析,傳遞給 parseEndTag 函數的三個引數分別是:標籤名以及結束標籤在 html 字串中起始和結束的位置,最後呼叫 continue 語句結束此次迴圈。
現在我們來講解下關鍵 parseEndTag 函數程式碼如下:
function parseEndTag(tagName, start, end) { var pos, lowerCasedTagName; if (start == null) { start = index; } if (end == null) { end = index; } // Find the closest opened tag of the same type if (tagName) { lowerCasedTagName = tagName.toLowerCase(); for (pos = stack.length - 1; pos >= 0; pos--) { if (stack[pos].lowerCasedTag === lowerCasedTagName) { break } } } else { // If no tag name is provided, clean shop pos = 0; } if (pos >= 0) { // Close all the open elements, up the stack for (var i = stack.length - 1; i >= pos; i--) { if (i > pos || !tagName && options.warn ) { options.warn( ("tag <" + (stack[i].tag) + "> has no matching end tag.") ); } if (options.end) { options.end(stack[i].tag, start, end); } } // Remove the open elements from the stack stack.length = pos; lastTag = pos && stack[pos - 1].tag; } else if (lowerCasedTagName === 'br') { if (options.start) { options.start(tagName, [], true, start, end); } } else if (lowerCasedTagName === 'p') { if (options.start) { options.start(tagName, [], false, start, end); } if (options.end) { options.end(tagName, start, end); } } }
你需要知道 parseEndTag 函數呼叫之前已經獲得到了結束標籤的名字以及結束標籤在html(template)字串中的起始和結束位置。 但是這並不代表著 html parser 結束了。
為什麼?
還記得我們之前講的 stack 棧嗎? 之前我們講到通過stack可以檢測是否有非一元標籤是否微寫閉合標籤,接下來還會處理 stack 棧中剩餘的標籤。
除了這些功能之外,parseEndTag函數還會做一件事兒,如果你感興趣你可以在任何html
檔案中寫下如下內容:
<body>
</br>
</p>
</body>
上面的html片段中,我們分別寫了</br>、</p>的結束標籤,但注意我們並沒有寫起始標籤,然後瀏覽器是能夠正常解析他們的,其中 </br> 標籤被正常解析為 <br> 標籤,而</p>標籤被正常解析為 <p></p> 。除了 br 與 p 其他任何標籤如果你只寫了結束標籤那麼瀏覽器都將會忽略。所以為了與瀏覽器的行為相同,parseEndTag 函數也需要專門處理br與p的結束標籤,即:</br> 和</p>。
當一個函數擁有兩個及以上功能的時候,最常用的技巧就是通過引數進行控制,還記得jQuery中的Access 嗎?parseEndTag 函數接收三個引數,這三個引數其實都是可選的,根據傳參的不同其功能也不同。
程式碼並不複雜我們一起來看下吧!
var pos, lowerCasedTagName; if (start == null) { start = index; } if (end == null) { end = index; }
定了兩個變數:pos和 lowerCasedTagName,其中變數 pos 會在後面用於判斷 html 字串是否缺少結束標籤,lowerCasedTagName 變數用來儲存 tagName 的小寫版。
接著是兩句if 語句,當 start 和 end 不存在時,將這兩個變數的值設定為當前字元流的讀入位置,即index。
所以當我們看到這兩個 if 語句時,我們就應該能夠想到:parseEndTag 函數的第二個引數和第三個引數都是可選的。
其實這種使用 parseEndTag 函數的方式我們在handleStartTag 函數中見過,當時我們沒有對其進行講解一起來回顧下。
if (expectHTML) { if (lastTag === 'p' && isNonPhrasingTag(tagName)) { parseEndTag(lastTag) } if (canBeLeftOpenTag(tagName) && lastTag === tagName) { parseEndTag(tagName) } }
我們知道 lastTag 參照的是stack
棧頂的元素,也就是最近(或者說上一次)遇到的開始標籤,所以如下判斷條件:
lastTag === 'p' && isNonPhrasingTag(tagName)
這裡想表達的意思是:最近一次遇到的開始標籤是 p 標籤,並且當前正在解析的開始標籤必須不能是段落式內容(Phrasing content)模型,這時候 if 語句塊的程式碼才會執行,即呼叫parseEndTag(lastTag)。
首先大家要知道每一個 html 元素都擁有一個或多個內容模型(content model),其中p 標籤本身的內容模型是流式內容(Flow content),並且 p 標籤的特性是隻允許包含段落式內容(Phrasing content)。
所以條件成立的情況如下:
<p><h1></h1></p>
在解析上面這段 html 字串的時候,首先遇到p標籤的開始標籤,此時lastTag被設定為 p ,緊接著會遇到 h1 標籤的開始標籤,由於 h2 標籤的內容模型屬於非段落式內容(Phrasing content)模型,所以會立即呼叫 parseEndTag(lastTag) 函數閉合 p 標籤,此時由於強行插入了</p> 標籤,所以解析後的字串將變為如下內容:
<p></p><h2></h2></p>
接著,繼續解析該字串,會遇到 <h2></h2> 標籤並正常解析之,最後解析器會遇到一個單獨的p 標籤的結束標籤,即:</p>。
這個時候就回到了我們前面講過的,當解析器遇到 p 標籤或者 br 標籤的結束標籤時會補全他們,最終<p><h2></h2></p> 這段 html 字串將被解析為:
<p></p><h2></h2><p></p>
而這也就是瀏覽器的行為,以上是第一個if 分支的意義。還有第二個if分支,它的條件如下:
canBeLeftOpenTag(tagName) && lastTag === tagName
以上條件成立的意思是:當前正在解析的標籤是一個可以省略結束標籤的標籤,並且與上一次解析到的開始標籤相同,如下:
<p>max
<p>kaixin
p 標籤是可以省略結束標籤的標籤,所以當解析到一個p標籤的開始標籤並且下一次遇到的標籤也是p標籤的開始標籤時,會立即關閉第二個p
標籤。即呼叫:parseEndTag(tagName) 函數,然後由於第一個p標籤缺少閉合標籤所以會Vue會給你一個警告。
接下來我們繼續講解handleStartTag函數後續的內容。
if (tagName) { lowerCasedTagName = tagName.toLowerCase(); for (pos = stack.length - 1; pos >= 0; pos--) { if (stack[pos].lowerCasedTag === lowerCasedTagName) { break } } } else { // If no tag name is provided, clean shop pos = 0; }
如果tagName存在,lowerCasedTagName 獲取的是 tagName 小寫之後的值,接下來開啟一個 for 迴圈從後向前遍歷 stack 棧,直到找到相應的位置,並且該位置索引會儲存到 pos 變數中,如果 tagName 不存在,則直接將 pos 設定為 0 。
開頭我們講到 pos 變數會被用來判斷是否有元素缺少閉合標籤。怎麼做到的呢?看完下面的程式碼你就明白了。
if (pos >= 0) { // Close all the open elements, up the stack for (var i = stack.length - 1; i >= pos; i--) { if (i > pos || !tagName && options.warn ) { options.warn( ("tag <" + (stack[i].tag) + "> has no matching end tag.") ); } if (options.end) { options.end(stack[i].tag, start, end); } } // Remove the open elements from the stack stack.length = pos; lastTag = pos && stack[pos - 1].tag; } else if (lowerCasedTagName === 'br') { if (options.start) { options.start(tagName, [], true, start, end); } } else if (lowerCasedTagName === 'p') { if (options.start) { options.start(tagName, [], false, start, end); } if (options.end) { options.end(tagName, start, end); } }
上面程式碼由三部分組成,即if...else if...else if。首先我們檢視 if 語句塊,當 pos >= 0 的時候就會走 if 語句塊。在 if 語句塊內開啟一個 for 迴圈,同樣是從後向前遍歷 stack 陣列,如果發現 stack 陣列中存在索引大於 pos 的元素,那麼該元素一定是缺少閉合標籤的,這個時候如果是在非生產環境那麼 Vue 便會列印一句警告,告訴你缺少閉合標籤。除了列印一句警告之外,隨後會呼叫 options.end(stack[i].tag, start, end) 立即將其閉合,這是為了保證解析結果的正確性。
stack.length = pos; lastTag = pos && stack[pos - 1].tag;
瞭解下剩下的兩個else if:
if (pos >= 0) { // ... 省略 } else if (lowerCasedTagName === 'br') { if (options.start) { options.start(tagName, [], true, start, end) } } else if (lowerCasedTagName === 'p') { if (options.start) { options.start(tagName, [], false, start, end) } if (options.end) { options.end(tagName, start, end) } }
這兩個else if 什麼情況下成立呢?
當你寫了 br 標籤的結束標籤:</br> 或 p 標籤的結束標籤 </p> 時,解析器能夠正常解析他們,其中對於 </br> 會將其解析為正常的 <br> 標籤,而 </p> 標籤也會正常解析為<p></p>。
可以發現對於 </br> 和 </p> 標籤瀏覽器可以將其正常解析為 <br> 以及<p></p>,Vue 的 parser 與瀏覽器的行為是一致的。
現在我們還剩一個問題沒有講解,即parseEndTag是如何處理stack棧中剩餘未處理的標籤的。其實就是呼叫 parseEndTag() 函數時不傳遞任何引數,也就是說此時 tagName 引數也不存在。這個時候我們再次檢視下面的程式碼:
由於 pos 為 0 ,所以 i >= pos 始終成立,這個時候 stack 棧中如果有剩餘未處理的標籤,則會逐個警告缺少閉合標籤,並呼叫 options.end 將其閉合。
以上對於整個詞法分析的過程重點部分就已經講解完畢了,其實現方式就是通過讀取字元流配合正則一點一點的解析字串,直到整個字串都被解析完畢為止。並且每當遇到一個特定的token 時都會呼叫相應的勾點函數,同時將有用的引數傳遞過去。比如每當遇到一個開始標籤都會呼叫 options.start 勾點函數,遇到閉合標籤呼叫 options.end 勾點函數。
下面我們來講講這兩個重要的勾點函數,並且談下AST的基本形成。
以上就是vue parseHTML函數解析器遇到結束標籤的詳細內容,更多關於vue parseHTML函數的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45