摘要:當字符串開頭是時,可以匹配匹配尾標簽。從結尾,找到所在位置批量閉合。
寫文章不容易,點個贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧
【Vue原理】Compile - 源碼版 之 標簽解析
咳咳,上一篇文章,我們已經大致把 parse 的流程給記錄了一遍,如果沒看過,比較建議,先把這個流程給看了
Compile - 源碼版 之 Parse 主要流程
但是忽略了其中的處理細節,比如標簽怎么解析的,屬性怎么解析的,而且這兩個內容也是非常多的,所以需要多帶帶拎出來詳細記錄,不然混在一起,又臭又長
白話版在這~ Compile - 白話版
今天的內容是,記錄 標簽解析的 源碼
首先,開篇之前呢,我們來了解一下文章會出現過的正則
相關正則var ncname = "[a-zA-Z_][w-.]*"; var qnameCapture = "((?:" + ncname + ":)?" + ncname + ")"; var startTagOpen = new RegExp(("^<" + qnameCapture)); var startTagClose = /^s*(/?)>/; var endTag = new RegExp(("^" + qnameCapture + "[^>]*>")); var attribute = /^s*([^s""<>/=]+)(?:s*(=)s*(?:"([^"]*)"+|"([^"]*)"+|([^s""=<>`]+)))?/;
主要是四個
startTagOpen匹配 頭標簽的 前半部分。當字符串開頭是 頭標簽時,可以匹配
startTagClose匹配 頭標簽的 右尖括號。當字符串開頭是 > 時,可以匹配
endTag匹配 尾標簽。當字符串開頭是 尾標簽時 可以匹配
attribute匹配標簽上的屬性。當字符串開頭是屬性則可以匹配
好的,看完上面四個正則, 心里有個 * 數之后,相信下面的內容你會更加清晰些
下面的內容分為
1、循環遍歷 template
2、處理 頭標簽
3、處理 尾標簽
那么我們按一個個來說
循環遍歷template通過上一篇內容,已經記錄過 是怎么循環遍歷 template 的了,就是通過 parseHTML 這個方法
這個方法,因為內容需要,也記錄一遍
首先,什么是循環遍歷template?
template 是一個字符串,所以每匹配完一個信息(比如頭標簽等),就會把template 截斷到匹配的結束位置
比如 template 是
"1111"
當我們匹配完了 頭標簽,那么 template 就會被截斷成
"1111
然后就這樣一直循環匹配新的 template,直到 template 被截斷成 空字符串,那么匹配完畢,其中跟截斷有關的一個重要函數就是 advance
這個函數在下面的源碼中用得非常多,需要牢記
其作用就是
1、截斷template
2、保存當前截斷的位置。比如你匹配了template到 字符串長度為5 的位置,那么 index 就是 4(從0開始)
function advance(n) { index += n; html = html.substring(n); }
記住這個函數哦,我傳入一個數字 n,就是要把 template 從 n 截取到結尾
然后下面就看看簡化的 parseHTML 源碼(如果嫌長,先跳到分析)
function parseHTML(html, options) { // 保存所有標簽的對象信息,tagName,attr,這樣,在解析尾部標簽的時候得到所屬的層級關系以及父標簽 var stack = []; var index = 0; var last; while (html) { last = html; var textEnd = html.indexOf("<"); // 如果開頭是 標簽的 < if (textEnd === 0) { /** * 如果開頭的 < 屬性尾標簽 * 比如 html = "
這段代碼已經簡化得很簡單了,算是整體對 template 處理的一種把控我覺得
先匹配 < 的位置
1 如果 < 在template開頭那么就是標簽(這里先不討論 字符串中的 <)
然后需要多一層判斷
如果是尾標簽的 <,那么交給 parseEndTag 處理
如果是頭標簽的 <,那么使用 handleStartTag 處理
2 如果 < 不在 template 開頭那么表明 開頭到 < 的這段位置是字符串,但是本文內容是標簽解析,所以忽略這部分
然后每完成一次匹配,就需要調用 advace 去截斷 template
然后現在,我們假定有下面這段處理
template = "111" parseHTML(template)
匹配 < 在開頭,正則判斷之后,發現不是 尾標簽的 <,那么需要判斷是不是 頭標簽的
然后使用 parseStartTag 方法去匹配頭標簽信息
匹配成功,使用 handleStartTag 方法處理
看到在 parseHTML 末尾聲明了三個函數,為了避免太長,我挑了出來放在相應的內容講
而之所以會在里面聲明這個三個函數,是為了在這三個函數中,能訪問到 parseHTML 中的變量,比如 stack,index
處理頭標簽 parseStartTag這個方法的作用就是
1、把頭標簽的所有信息集合起來,包括屬性,標簽名等
2、匹配完成之后同樣調用 advance 去截斷 template
3、把標簽信息 返回
源碼已經簡化,并且有做流程注釋,大家肯定看得懂,太煩的可以看后面的結果
function parseStartTag() { // html ="111" // start = ["111" advance(start[0].length); var end, attr; // 循環匹配 屬性 內容,保存屬性列表 // 直到 template 開頭是 頭標簽的 > while ( // 匹配不到頭標簽的 >,開始匹配 屬性內容 // end = null ! (end = html.match(startTagClose)) && // 開始匹配 屬性內容 // attr = ["name=1", "name", "=" ] (attr = html.match(attribute)) ) { advance(attr[0].length); match.attrs.push(attr); } // 匹配到 起始標簽的 >,標簽屬性那些已經匹配完畢了 // 返回收集到的 標簽信息 if (end) { advance(end[0].length); // 如果是單標簽,那么 unarySlash 的值是 /,比如 match.unarySlash = end[1]; match.end = index; return match } } }
我們來記錄下這個方法會返回什么
比如
html = ""
parseStartTag 處理之后會返回以下內容
{ tagName: "div", attrs: [ [" name=1", "name", "=" , undefined, undefined, "1"] ], unarySlash: "", start: 0, end: 12 }
其中 的屬性
start:頭標簽的 < 在 template 中的位置
end:頭標簽的 > 在 tempalte 中的位置
attrs:是一個二維數組,存放著所有頭標簽的 屬性信息
unarySlash:表示這個標簽是否是 單標簽。如果是 true,那么不是單標簽。如果是 false,那么就是單標簽。一切在于匹配頭標簽時,有沒有匹配到 /
通過 parseHTML 我們看到,parseStartTag 返回的 頭標簽信息,給了誰呢?
沒錯,傳給了 handleStartTag
handleStartTag這個函數的作用
1、接收上一步收集的標簽信息
2、處理屬性,轉換一下其格式
3、保存進 stack,記錄 DOM 父子結構順序
function handleStartTag(match) { var tagName = match.tagName; var unarySlash = match.unarySlash; // 判斷是不是單標簽,input,img 這些 var unary = isUnaryTag$$1(tagName) || !!unarySlash; var l = match.attrs.length; var attrs = new Array(l); // 把屬性數組轉換成對象 for (var i = 0; i < l; i++) { var args = match.attrs[i]; // args = [" name=1", "name", "=", undefined, undefined, "1" ] var value = args[3] || args[4] || args[5] || ""; attrs[i] = { name: args[1], value: value }; } // 不是單標簽,才存到 stack if (!unary) { stack.push({ tag: tagName, attrs: attrs }); } if (options.start) { options.start( tagName, attrs, unary, match.start, match.end ); } }
最后,把該標簽得到的信息,傳給 options.start,幫助建立 template 的 ast(在 上篇文章 Compile - 源碼版 之 Parse 主要流程 中有說明=)
那么到這里,頭標簽 匹配完了
然后 template 被截斷成
"111"
文本處理的部分我們跳過,跳到尾標簽,所以 template 為
""
然后匹配到尾標簽,交給 parseEndTag 處理
那么進入我們的下一小節內容,處理尾標簽
處理尾標簽在 parseHTML 中看到
當使用 endTag 這個正則成功匹配到尾標簽時,會調用 parseEndTag
而 這個函數呢,可能沒有那么好理解了,你可以先跳過源碼,翻到后面的解析
function parseEndTag(tagName, start, end) { var pos, lowerCasedTagName; // 從stack 最后查找匹配的 tagName 位置 if (tagName) { // 如果在 stack 中找不到,pos 最后是 -1 for (pos = stack.length - 1; pos >= 0; pos--) { if (stack[pos].tagName===tagName) break } } else { // 如果沒有提供標簽名,那么關閉所有存在 stack 中的 起始標簽 pos = 0 } // 批量 stack pos 位置后的所有標簽 if (pos >= 0) { // 關閉 pos 位置之后所有的起始標簽,避免有些標簽沒有尾標簽 // 比如 stack.len = 7 , pos=5 ,那么就關閉 最后兩個 for (var i = stack.length - 1; i >= pos; i--) { if (options.end) { options.end(stack[i].tag, start, end); } } // 匹配完閉合標簽之后,就把 匹配了的標簽頭 給 移除了 stack.length = pos; } }
函數功能分為兩部分
1、找位置。從 stack 結尾,找到 tagName 所在位置 pos
2、批量閉合。閉合并移除 stack 在 pos 位置后的所有 tag
現在我們先給一個模板,然后慢慢解釋
現在已經連續匹配到三個 頭標簽,div,header,span
此時 stack= [ div, header, span ]
然后開始匹配到 ,然后去 stack 末尾找 span
確定 span 在 stack 的位置 pos 后,批量閉合stack 的 pos 后的所有標簽
為什么從末尾開始?因為 stack 是按 template 的標簽順序存放的,肯定是先匹配到父標簽,再匹配到子標簽
碰到 尾標簽,肯定找最近匹配到的頭標簽,那么肯定是剛存入 stack 的,那么就是在 stack 的結尾
為什么閉合 pos位置后所有標簽?因為怕有刁民不寫閉合標簽,比如模板是這樣
同樣,匹配完三個頭標簽
stack = [ div, header, span ]
接著匹配到 ,于是在 stack 末尾中找 header
在倒數第二個,那么 pos = 1
根據 stack 的長度, 遍歷一次 stack,閉合到末尾,就是閉合 stack[1], stack[2]
就是閉合 header 和 span 了
就是為了避免有屁民沒寫 尾標簽
你說單標簽沒有尾標簽啊?是啊,但是單標簽 不存進 stack 啊哈哈哈
在 handleStartTag 中有處理哦
接下來你就可以去看 parseEndTag 的源碼了,肯定能看懂
怎么閉合的呢?parseEndTag 就做了匹配 tag 位置 和容錯處理
主要實現閉合功能在調用 options.end 中,通過不斷地傳入尾標簽從而完成閉合功能
如果你看過上篇文章 Compile - 源碼版 之 Parse 主要流程 就知道,閉合是為了形成正確的節點關系樹
也可以說,是為了明確節點的父子關系
總結通過這次我們知道了
1、標簽的匹配方法
2、怎么利用頭標簽收集信息
3、閉合標簽的處理方法
最后鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當的地方,歡迎后臺聯系本人,有重謝
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106588.html
寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Compile - 源碼版 之 Parse 主要流程 本文難度較繁瑣,需要耐心觀看,如果你對 compile 源碼暫時...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之屬性解析哈哈哈,今天終 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
摘要:一旦我們檢測到這些子樹,我們可以把它們變成常數,這樣我們就不需要了在每次重新渲染時為它們創建新的節點在修補過程中完全跳過它們。否則,吊裝費用將會增加好處大于好處,最好總是保持新鮮。 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之節點數據拼接上一篇我們 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
摘要:還原的難度就在于變成模板了,因為其他的什么等是原封不動的哈哈,可是直接照抄最后鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當的地方,歡迎后臺聯系本人,有重謝 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版...
閱讀 3948·2021-09-24 10:24
閱讀 1386·2021-09-22 16:01
閱讀 2713·2021-09-06 15:02
閱讀 1013·2019-08-30 13:01
閱讀 1002·2019-08-30 10:52
閱讀 632·2019-08-29 16:36
閱讀 2231·2019-08-29 12:51
閱讀 2332·2019-08-28 18:29