我們現(xiàn)在要講述的是當(dāng)解析器遇到一個(gè)文本節(jié)點(diǎn)時(shí)會(huì)如何為文本節(jié)點(diǎn)創(chuàng)建元素描述對(duì)象,那又該作何“處理”。
parseHTML(template, { chars: function(){ //... }, //... })
chars源碼:
chars: function chars(text) { if (!currentParent) { { if (text === template) { warnOnce( 'Component template requires a root element, rather than just text.' ); } else if ((text = text.trim())) { warnOnce( ("text \"" + text + "\" outside root element will be ignored.") ); } } return } // IE textarea placeholder bug /* istanbul ignore if */ if (isIE && currentParent.tag === 'textarea' && currentParent.attrsMap.placeholder === text ) { return } var children = currentParent.children; text = inPre || text.trim() ? isTextTag(currentParent) ? text : decodeHTMLCached(text) // only preserve whitespace if its not right after a starting tag : preserveWhitespace && children.length ? ' ' : ''; if (text) { var res; if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) { children.push({ type: 2, expression: res.expression, tokens: res.tokens, text: text }); } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { children.push({ type: 3, text: text }); } } }
當(dāng)解析器遇到文本節(jié)點(diǎn)時(shí),如上代碼中的 chars 鉤子函數(shù)就會(huì)被調(diào)用,并且接收該文本節(jié)點(diǎn)的文本內(nèi)容作為參數(shù)。
我們來看chars鉤子函數(shù)最開始的這段代碼:
if (!currentParent) { { if (text === template) { warnOnce( 'Component template requires a root element, rather than just text.' ); } else if ((text = text.trim())) { warnOnce( ("text \"" + text + "\" outside root element will be ignored.") ); } } return }
先確定 currentParent 變量存在與否,指向的是當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn):。
如果 currentParent 變量不存在說明什么問題?
1:沒有根元素,只有文本。
2: 文本在根元素之外。
當(dāng)遇到第一種情況打印警告信息:"模板必須要有根元素",第二種情況打印警告信息:" 根元素外的文本將會(huì)被忽略"。
接下來:
if (isIE && currentParent.tag === 'textarea' && currentParent.attrsMap.placeholder === text ) { return }
其實(shí)上面的代碼就是解決 IE 瀏覽器中渲染 <textarea> 標(biāo)簽的 placeholder 屬性時(shí)存在的 bug 的。現(xiàn)在我們看看issue。
接下來是個(gè)嵌套三元表達(dá)式:
var children = currentParent.children; text = inPre || text.trim() ? isTextTag(currentParent) ? text : decodeHTMLCached(text) // only preserve whitespace if its not right after a starting tag : preserveWhitespace && children.length ? ' ' : '';
上面的嵌套三元表達(dá)式判斷就是為了明確條件 inPre || text.trim() 的真假,當(dāng)結(jié)果為 true,檢測(cè)了當(dāng)前文本節(jié)點(diǎn)的父節(jié)點(diǎn)是否是文本標(biāo)簽,當(dāng)文本標(biāo)簽則直接使用原始文本,否則使用decodeHTMLCached 函數(shù)對(duì)文本進(jìn)行解碼。
假如反饋inPre || text.trim() 如果為 false,檢測(cè) preserveWhitespace 是否為 true 。preserveWhitespace 簡(jiǎn)單來說就是一個(gè)布爾值代表著是否保留空格,僅在返回為它為 false的情況下才會(huì)保留空格。但即使 preserveWhitespace 常量的值為真,如果當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)沒有子元素則也不會(huì)保留空格,換句話說,編譯器只會(huì)保留那些不存在于開始標(biāo)簽之后的空格。
接下來:
if (text) { var res; if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) { children.push({ type: 2, expression: res.expression, tokens: res.tokens, text: text }); } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { children.push({ type: 3, text: text }); } }
這里相當(dāng)就比較簡(jiǎn)單了一個(gè) if else if 操作,第一個(gè) if 判斷當(dāng)前元素未使用v-pre 指令,text不為空,使用 parseText 函數(shù)成功解析當(dāng)前文本節(jié)點(diǎn)的內(nèi)容。
對(duì)于前兩個(gè)條件很好理解,關(guān)鍵在于 parseText 函數(shù)能夠成功解析文本節(jié)點(diǎn)的內(nèi)容說明了什么,如下示例代碼:
<div> hello: {{ message }} </div>
如上模板中存在的文本節(jié)點(diǎn)包含了 Vue 語法中的字面量表達(dá)式,而 parseText 函數(shù)的作用就是用來解析這段包含了字面量表達(dá)式的文本的。此時(shí)會(huì)執(zhí)行以下代碼創(chuàng)建一個(gè)類型為2(type = 2) 的元素描述對(duì)象:
children.push({ type: 2, expression: res.expression, tokens: res.tokens, text: text });
注意:類型為 2 的元素描述對(duì)象擁有三個(gè)特殊的屬性,分別是 expression 、tokens 以及text ,其中 text 就是原始的文本內(nèi)容,而 expression 和 tokens 的值是通過 parseText 函數(shù)解析的結(jié)果中讀取的。
現(xiàn)在講講parseText函數(shù),當(dāng)出現(xiàn) if 判斷失敗出現(xiàn)的三種可能性。
當(dāng)前解析的元素使用v-pre 指令
text 為空
parseText 解析失敗
只要以上三種情況中,有一種情況出現(xiàn)則代碼會(huì)來到else...if 分支的判斷,如下:
else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { children.push({ type: 3, text: text }); }
如果滿足 else if 中的條件直接,創(chuàng)建一個(gè)類型為3(type = 3) 的元素描述對(duì)象:類型為3 的元素描述對(duì)象只擁有一個(gè)的屬性text存儲(chǔ)原始的文本內(nèi)容。
在看下要滿足 else if 中的這些條件吧!
文本內(nèi)容不是空格
文本內(nèi)容是空格,但是該文本節(jié)點(diǎn)的父節(jié)點(diǎn)還沒有子節(jié)點(diǎn)(即 !children.length )
文本內(nèi)容是空格,并且該文本節(jié)點(diǎn)的父節(jié)點(diǎn)有子節(jié)點(diǎn),但最后一個(gè)子節(jié)點(diǎn)不是空格
接下來我們來聊聊之前講到的parseText 函數(shù)。
parseText
function parseText( text, delimiters ) { var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE; if (!tagRE.test(text)) { return } var tokens = []; var rawTokens = []; var lastIndex = tagRE.lastIndex = 0; var match, index, tokenValue; while ((match = tagRE.exec(text))) { index = match.index; // push text token if (index > lastIndex) { rawTokens.push(tokenValue = text.slice(lastIndex, index)); tokens.push(JSON.stringify(tokenValue)); } // tag token var exp = parseFilters(match[1].trim()); tokens.push(("_s(" + exp + ")")); rawTokens.push({ '@binding': exp }); lastIndex = index + match[0].length; } if (lastIndex < text.length) { rawTokens.push(tokenValue = text.slice(lastIndex)); tokens.push(JSON.stringify(tokenValue)); } return { expression: tokens.join('+'), tokens: rawTokens } }
parseText 接收兩個(gè)參數(shù) text 要解析的文本,delimiters 是編譯器的一個(gè)用戶自定義選項(xiàng)delimiters,通過它可以改變文本插入分隔符。所以才有了如下代碼。
var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE;
這里是解析文本所用正則之間的一個(gè)較量,delimiters 有值就調(diào)用buildRegex函數(shù),我們默認(rèn)是沒有值,使用 defaultTagRE 來解析文本。
var defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
這個(gè)正則還是非常簡(jiǎn)單,接下來會(huì)判斷,如果文本中沒有與正則相匹配的文本直接直接終止函數(shù)的執(zhí)行。
if (!tagRE.test(text)) { return }
接下來代碼就有意思了一起看下。
var tokens = []; var rawTokens = []; var lastIndex = tagRE.lastIndex = 0; var match, index, tokenValue; while ((match = tagRE.exec(text))) { index = match.index; // push text token if (index > lastIndex) { rawTokens.push(tokenValue = text.slice(lastIndex, index)); tokens.push(JSON.stringify(tokenValue)); } // tag token var exp = parseFilters(match[1].trim()); tokens.push(("_s(" + exp + ")")); rawTokens.push({ '@binding': exp }); lastIndex = index + match[0].length; } if (lastIndex < text.length) { rawTokens.push(tokenValue = text.slice(lastIndex)); tokens.push(JSON.stringify(tokenValue)); } return { expression: tokens.join('+'), tokens: rawTokens }
這段代碼不難,初始定義了一系列變量。 接著開啟一個(gè)while循環(huán),使用 tagRE 正則匹配文本內(nèi)容,并將匹配結(jié)果保存在 match 變量中,直到匹配失敗循環(huán)才會(huì)終止,這時(shí)意味著所有的字面量表達(dá)式都已經(jīng)處理完畢了。
在這個(gè)while循環(huán)結(jié)束返回一個(gè)對(duì)象,expression、tokens分別存儲(chǔ)解析過程中的信息。
假設(shè)文本如下:
<div id="app">hello {{ message }}</div>
parseText 解析文本后返回的對(duì)象。
{ expression: "'hello'+_s(message)", tokens: [ 'hello', { '@binding': 'message' } ] }
接下來我們聊聊對(duì)結(jié)束標(biāo)簽的處理。
end 源碼
end: function end() { // remove trailing whitespace var element = stack[stack.length - 1]; var lastNode = element.children[element.children.length - 1]; if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) { element.children.pop(); } // pop stack stack.length -= 1; currentParent = stack[stack.length - 1]; closeElement(element); }
end 鉤子函數(shù),當(dāng)解析 html 字符串遇到結(jié)束標(biāo)簽的時(shí)候,。那么在 end 鉤子函數(shù)中都需要做哪些事情呢?
在之前的文章中我們講過解析器遇到非一元標(biāo)簽的開始標(biāo)簽時(shí),會(huì)將該標(biāo)簽的元素描述對(duì)象設(shè)置給 currentParent 變量,代表后續(xù)解析過程中遇到的所有標(biāo)簽都應(yīng)該是 currentParent 變量所代表的標(biāo)簽的子節(jié)點(diǎn),同時(shí)還會(huì)將該標(biāo)簽的元素描述對(duì)象添加到 stack 棧中。
而當(dāng)遇到結(jié)束標(biāo)簽的時(shí)候則意味著 currentParent 變量所代表的標(biāo)簽以及其子節(jié)點(diǎn)全部解析完畢了,此時(shí)我們應(yīng)該把 currentParent 變量的引用修改為當(dāng)前標(biāo)簽的父標(biāo)簽,這樣我們就將作用域還原給了上層節(jié)點(diǎn),以保證解析過程中正確的父子關(guān)系。
下面代碼就是來完成這個(gè)工作:
stack.length -= 1;
currentParent = stack[stack.length - 1];
closeElement(element);
首先將當(dāng)前節(jié)點(diǎn)出棧:stack.length -= 1 什么意思呢?
看一個(gè)代碼就懂了。
var arr = [1,2,3,4]; arr.length-=1; >arr [1,2,3]
接著讀取出棧后 stack 棧中的最后一個(gè)元素作為 currentParent 變量的值。 那closeElement 函數(shù)是做什么用的呢?
closeElement 源碼
function closeElement(element) { // check pre state if (element.pre) { inVPre = false; } if (platformIsPreTag(element.tag)) { inPre = false; } // apply post-transforms for (var i = 0; i < postTransforms.length; i++) { postTransforms[i](element, options); } }
closeElement 的作用有兩個(gè):第一個(gè)是對(duì)數(shù)據(jù)狀態(tài)的還原,第二個(gè)是調(diào)用后置處理轉(zhuǎn)換鉤子函數(shù)。
接下來看下end函數(shù)中剩余代碼:
// remove trailing whitespace var element = stack[stack.length - 1]; var lastNode = element.children[element.children.length - 1]; if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) { element.children.pop(); }
這個(gè)代碼的作用是去除當(dāng)前元素最后一個(gè)空白子節(jié)點(diǎn),我們?cè)谥v解 chars 鉤子函數(shù)時(shí)了解到:preserveWhitespace 只會(huì)保留那些不在開始標(biāo)簽之后的空格,所以當(dāng)空白作為標(biāo)簽的最后一個(gè)子節(jié)點(diǎn)存在時(shí),也會(huì)被保留,如下代碼所示:
<div><span>test</span> <!-- 空白占位 --> </div>
如上代碼中 <span> 標(biāo)簽的結(jié)束標(biāo)簽與 <div> 標(biāo)簽的結(jié)束標(biāo)簽之間存在一段空白,這段空白將會(huì)被保留。要知道這段空白在被保留下來后,就會(huì)對(duì)于布局產(chǎn)生影響,尤其是對(duì)行內(nèi)元素的影響。
為了消除這些影響帶來的問題,好的做法是將它們?nèi)サ簦绱a就是用來完成這個(gè)工作的。
comment 注釋節(jié)點(diǎn)描述對(duì)象
解析器是否會(huì)解析并保留注釋節(jié)點(diǎn),是由 shouldKeepComment 編譯器選項(xiàng)決定的,開發(fā)者可以在創(chuàng)建Vue 實(shí)例的時(shí)候通過設(shè)置 comments 選項(xiàng)的值來控制編譯器的shouldKeepComment 選項(xiàng)。默認(rèn)情況下 comments 選項(xiàng)的值為 false ,即不保留注釋,假如將其設(shè)置為 true ,則當(dāng)解析器遇到注釋節(jié)點(diǎn)時(shí)會(huì)保留該注釋節(jié)點(diǎn),此時(shí) parseHTML 函數(shù)的 comment 鉤子函數(shù)會(huì)被調(diào)用,如下:
comment: function comment(text) { currentParent.children.push({ type: 3, text: text, isComment: true }); }
注意事項(xiàng):要對(duì)普通文本節(jié)點(diǎn)作區(qū)分的話,當(dāng)普通文本節(jié)點(diǎn)與注釋節(jié)點(diǎn)的元素描述對(duì)象的類型是一樣的都是 3 ,且在不同的是注釋節(jié)點(diǎn)的元素描述對(duì)象擁有 isComment 屬性,并且該屬性的值為 true。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/127972.html
vue parseHTML函數(shù)解析器遇到結(jié)束標(biāo)簽,在之前文章中已講述完畢。 例如有html(template)字符串: <divid="app"> <p>{{message}}</p> </div> 產(chǎn)出如下: { attrs:["id="app"","id...
摘要:模板解析器原理本文來自深入淺出模板編譯原理篇的第九章,主要講述了如何將模板解析成,這一章的內(nèi)容是全書最復(fù)雜且燒腦的章節(jié)。循環(huán)模板的偽代碼如下截取模板字符串并觸發(fā)鉤子函數(shù)為了方便理解,我們手動(dòng)模擬解析器的解析過程。 Vue.js 模板解析器原理 本文來自《深入淺出Vue.js》模板編譯原理篇的第九章,主要講述了如何將模板解析成AST,這一章的內(nèi)容是全書最復(fù)雜且燒腦的章節(jié)。本文未經(jīng)排版,真...
摘要:當(dāng)前正在處理的節(jié)點(diǎn),以及該節(jié)點(diǎn)的和等信息。源碼解析之一整體分析源碼解析之三寫作中源碼解析之四寫作中作者博客作者作者微博 筆者系 vue-loader 貢獻(xiàn)者之一(#16) 前言 vue-loader 源碼解析系列之一,閱讀該文章之前,請(qǐng)大家首先參考大綱 vue-loader 源碼解析系列之 整體分析 selector 做了什么 const path = require(path) co...
在之前文章中我們講述了parseHTML 函數(shù)源碼解析拿到返回值后的處理,這篇文章就為我們講述了當(dāng) textEnd === 0 解析器遇到結(jié)束標(biāo)簽,parse 結(jié)束標(biāo)簽的代碼如下: //Endtag: varendTagMatch=html.match(endTag); if(endTagMatch){ varcurIndex=index; advance(endTagMat...
寫文章不容易,點(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í)...
閱讀 557·2023-03-27 18:33
閱讀 745·2023-03-26 17:27
閱讀 642·2023-03-26 17:14
閱讀 599·2023-03-17 21:13
閱讀 533·2023-03-17 08:28
閱讀 1815·2023-02-27 22:32
閱讀 1312·2023-02-27 22:27
閱讀 2193·2023-01-20 08:28