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