關于parseHTML 函數(shù)源碼解析 AST 相關知識已做過介紹,下面可以看看Vue start鉤子函數(shù)源碼。
start: function start(tag, attrs, unary) { // check namespace. // inherit parent ns if there is one var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag); // handle IE svg bug /* istanbul ignore if */ if (isIE && ns === 'svg') { attrs = guardIESVGBug(attrs); } var element = createASTElement(tag, attrs, currentParent); if (ns) { element.ns = ns; } if (isForbiddenTag(element) && !isServerRendering()) { element.forbidden = true; warn$2( 'Templates should only be responsible for mapping the state to the ' + 'UI. Avoid placing tags with side-effects in your templates, such as ' + "<" + tag + ">" + ', as they will not be parsed.' ); } // apply pre-transforms for (var i = 0; i < preTransforms.length; i++) { element = preTransforms[i](element, options) || element; } if (!inVPre) { processPre(element); if (element.pre) { inVPre = true; } } if (platformIsPreTag(element.tag)) { inPre = true; } if (inVPre) { processRawAttrs(element); } else if (!element.processed) { // structural directives processFor(element); processIf(element); processOnce(element); // element-scope stuff processElement(element, options); } function checkRootConstraints(el) { { if (el.tag === 'slot' || el.tag === 'template') { warnOnce( "Cannot use <" + (el.tag) + "> as component root element because it may " + 'contain multiple nodes.' ); } if (el.attrsMap.hasOwnProperty('v-for')) { warnOnce( 'Cannot use v-for on stateful component root element because ' + 'it renders multiple elements.' ); } } } // tree management if (!root) { root = element; checkRootConstraints(root); } else if (!stack.length) { // allow root elements with v-if, v-else-if and v-else if (root.if && (element.elseif || element.else)) { checkRootConstraints(element); addIfCondition(root, { exp: element.elseif, block: element }); } else { warnOnce( "Component template should contain exactly one root element. " + "If you are using v-if on multiple elements, " + "use v-else-if to chain them instead." ); } } if (currentParent && !element.forbidden) { if (element.elseif || element.else) { processIfConditions(element, currentParent); } else if (element.slotScope) { // scoped slot currentParent.plain = false; var name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element; } else { currentParent.children.push(element); element.parent = currentParent; } } if (!unary) { currentParent = element; stack.push(element); } else { closeElement(element); } }
在上面的代碼中start 鉤子函數(shù)能接受的參數(shù)有三個,分別是 tag 標簽名稱,attrs表示該標簽的屬性數(shù)組,和unary是代表著是否是一元標簽的標識 。
不懂的不要擔心,現(xiàn)在我們一起來解析函數(shù)體中的代碼。
var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
上面的開頭定義了標簽的命名空間的 ns 變量,主要是為獲取當前元素的命名空間,那如何實現(xiàn)?我們要首先檢測currentParent 變量是否存在,眾所周知當前元素的父級元素描述對象就是 currentParent 變量,比如,當前父級元素存在命名空間,那當前元素的命名空間就是父級的命名空間名稱。
假如父級元素不存在或父級元素沒有命名空間,調用platformGetTagNamespace函數(shù),platformGetTagNamespace 函數(shù)顯示獲取svg 和 math 這兩個標簽的命名空間,它們兩個的命名空間是繼承所有子標簽。
platformGetTagNamespace 源碼
function getTagNamespace(tag) { if (isSVG(tag)) { return "svg" } if (tag === "math") { return "math" } } 接下來源碼: 1 2 3 if (isIE && ns === "svg") { attrs = guardIESVGBug(attrs); }
這里通過isIE來判斷宿主環(huán)境是不是IE瀏覽器,并且前元素的命名空間為svg, 如果是通過guardIESVGBug處理當前元素的屬性數(shù)組attrs,并使用處理后的結果重新賦值給attrs變量,該問題是svg標簽中渲染多余的屬性,如下svg標簽:
<svg xmlns:feature="http://www.openplans.org/topp"></svg>
被渲染為:
<svg xmlns:NS1="" NS1:xmlns:feature="http://www.openplans.org/topp"></svg>
標簽中多了 'xmlns:NS1="" NS1:' 這段字符串,解決辦法也很簡單,將整個多余的字符串去掉即可。而 guardIESVGBug 函數(shù)就是用來修改NS1:xmlns:feature屬性并移除xmlns:NS1="" 屬性的。
接下來源碼:
var element = createASTElement(tag, attrs, currentParent); if (ns) { element.ns = ns; }
在上章節(jié)聊過,createASTElement 它將生成當前標簽的元素描述對象并且賦值給 element 變量。緊接著檢查當前元素是否存在命名空間 ns ,如果存在則在元素對象上添加 ns 屬性,其值為命名空間的值。
接下來源碼:
if (isForbiddenTag(element) && !isServerRendering()) { element.forbidden = true; warn$2( 'Templates should only be responsible for mapping the state to the ' + 'UI. Avoid placing tags with side-effects in your templates, such as ' + "<" + tag + ">" + ', as they will not be parsed.' ); }
這里的作用就是判斷在非服務端渲染情況下,當前解析的開始標簽是否是禁止在模板中使用的標簽。哪些是禁止的呢?
isForbiddenTag 函數(shù)
function isForbiddenTag(el) { return ( el.tag === 'style' || (el.tag === 'script' && ( !el.attrsMap.type || el.attrsMap.type === 'text/javascript' )) ) }
可以看到,style,script 都是在禁止名單中,但通過isForbiddenTag 也發(fā)現(xiàn)一個彩蛋。
<script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script>
當定義模板的方式如上,在 <script> 元素上添加 type="text/x-template" 屬性。 此時的script不會被禁止。
最后還會在當前元素的描述對象上添加 element.forbidden 屬性,并將其值設置為true。
接下來源碼:
for (var i = 0; i < preTransforms.length; i++) { element = preTransforms[i](element, options) || element; }
如上代碼中使用 for 循環(huán)遍歷了preTransforms 數(shù)組,preTransforms 是通過pluckModuleFunction 函數(shù)從options.modules 選項中篩選出名字為preTransformNode 函數(shù)所組成的數(shù)組。實際上 preTransforms 數(shù)組中只有一個 preTransformNode 函數(shù)該函數(shù)只用來處理 input 標簽我們在后面章節(jié)會來講它。
接下來源碼:
if (!inVPre) { processPre(element); if (element.pre) { inVPre = true; } } if (platformIsPreTag(element.tag)) { inPre = true; } if (inVPre) { processRawAttrs(element); } else if (!element.processed) { // structural directives processFor(element); processIf(element); processOnce(element); // element-scope stuff processElement(element, options); }
可以看到這里會有大量的process*的函數(shù),這些函數(shù)是做什么用的呢?實際上process* 系列函數(shù)的作用就是對元素描述對象做進一步處理,比如其中一個函數(shù)叫做 processPre,這個函數(shù)的作用就是用來檢測元素是否擁有v-pre 屬性,如果有v-pre 屬性則會在 element 描述對象上添加一個 pre 屬性,如下:
{ type: 1, tag, attrsList: attrs, attrsMap: makeAttrsMap(attrs), parent, children: [], pre: true }
總結:所有process* 系列函數(shù)的作用都是為了讓一個元素的描述對象更加充實,使這個對象能更加詳細地描述一個元素, 不過我們本節(jié)主要總結解析一個開始標簽需要做的事情,所以稍后去看這些代碼的實現(xiàn)。
接下來源碼:
function checkRootConstraints(el) { { if (el.tag === 'slot' || el.tag === 'template') { warnOnce( "Cannot use <" + (el.tag) + "> as component root element because it may " + 'contain multiple nodes.' ); } if (el.attrsMap.hasOwnProperty('v-for')) { warnOnce( 'Cannot use v-for on stateful component root element because ' + 'it renders multiple elements.' ); } } }
我們知道在編寫 Vue 模板的時候會受到兩種約束,首先模板必須有且僅有一個被渲染的根元素,第二不能使用 slot 標簽和 template 標簽作為模板的根元素。
checkRootConstraints 函數(shù)內部首先通過判斷 el.tag === 'slot' || el.tag === 'template' 來判斷根元素是否是slot 標簽或 template 標簽,如果是則打印警告信息。接
著又判斷當前元素是否使用了 v-for 指令,因為v-for 指令會渲染多個節(jié)點所以根元素是不允許使用 v-for 指令的。
接下來源碼:
if (!root) { root = element; checkRootConstraints(root); } else if (!stack.length) { // allow root elements with v-if, v-else-if and v-else if (root.if && (element.elseif || element.else)) { checkRootConstraints(element); addIfCondition(root, { exp: element.elseif, block: element }); } else { warnOnce( "Component template should contain exactly one root element. " + "If you are using v-if on multiple elements, " + "use v-else-if to chain them instead." ); } }
這個 if 語句先檢測 root 是否存在!我們知道 root 變量在一開始是不存在的,如果 root 不存在那說明當前元素應該就是根元素,所以在 if 語句塊內直接把當前元素的描述對象 element 賦值給 root 變量,同時會調用 checkRootConstraints函數(shù)檢查根元素是否符合要求。
再來看 else if 語句的條件,當 stack 為空的情況下會執(zhí)行 else if 語句塊內的代碼, 那stack 什么情況下才為空呢?前面已經多次提到每當遇到一個非一元標簽時就會將該標簽的描述對象放進數(shù)組,并且每當遇到一個結束標簽時都會將該標簽的描述對象從 stack 數(shù)組中拿掉,那也就是說在只有一個根元素的情況下,正常解析完成一段 html 代碼后 stack 數(shù)組應該為空,或者換個說法,即當 stack 數(shù)組被清空后則說明整個模板字符串已經解析完畢了,但此時 start 鉤子函數(shù)仍然被調用了,這說明模板中存在多個根元素,這時 else if 語句塊內的代碼將被執(zhí)行:
接下來源碼:
if (root.if && (element.elseif || element.else)) { checkRootConstraints(element); addIfCondition(root, { exp: element.elseif, block: element }); } else { warnOnce( "Component template should contain exactly one root element. " + "If you are using v-if on multiple elements, " + "use v-else-if to chain them instead." ); }
想要能看懂這個代碼,你需要懂一些前置知識。
[ Vue條件渲染 ] (https://cn.vuejs.org/v2/guide/conditional.html)
我們知道在編寫 Vue 模板時的約束是必須有且僅有一個被渲染的根元素,但你可以定義多個根元素,只要能夠保證最終只渲染其中一個元素即可,能夠達到這個目的的方式只有一種,那就是在多個根元素之間使用 v-if 或 v-else-if 或 v-else 。
示例代碼:
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>
在回歸到代碼部分。
if (root.if && (element.elseif || element.else))
root 對象中的 .if 屬性、.elseif 屬性以及 .else 屬性都是哪里來的,它們是在通過 processIf 函數(shù)處理元素描述對象時,如果發(fā)現(xiàn)元素的屬性中有 v-if 或 v-else-if 或 v-else ,則會在元素描述對象上添加相應的屬性作為標識。
上面代碼如果第一個根元素上有 .if 的屬性,而非第一個根元素 element 有 .elseif 屬性或者 .else 屬性,這說明根元素都是由 v-if、v-else-if、v-else 指令控制的,同時也保證了被渲染的根元素只有一個。
接下來繼續(xù)看:
if (root.if && (element.elseif || element.else)) { checkRootConstraints(element); addIfCondition(root, { exp: element.elseif, block: element }); } else { warnOnce( "Component template should contain exactly one root element. " + "If you are using v-if on multiple elements, " + "use v-else-if to chain them instead." ); }
checkRootConstraints 函數(shù)檢查當前元素是否符合作為根元素的要求,這都能理解。
addIfCondition是什么
看下它的源代碼。
function addIfCondition(el, condition) { if (!el.ifConditions) { el.ifConditions = []; } el.ifConditions.push(condition); }
代碼很簡單,調用addIfCondition 傳遞的參數(shù) root 對象,在函數(shù)體中擴展一個屬性addIfCondition, root.addIfCondition 屬性值是一個對象。 此對象中有兩個屬性exp、block。實際上該函數(shù)是一個通用的函數(shù),不僅僅用在根元素中,它用在任何由 v-if、v-else-if 以及 v-else 組成的條件渲染的模板中。
通過如上分析我們可以發(fā)現(xiàn),具有 v-else-if 或 v-else 屬性的元素的描述對象會被添加到具有 v-if 屬性的元素描述對象的 .ifConnditions 數(shù)組中。
舉個例子,如下模板:
<div v-if="A"></div> <div v-else-if="B"></div> <div v-else-if="C"></div> <div v-else></div>
解析后生成的 AST 如下(簡化版):
{ type: 1, tag: 'div', ifConditions: [ { exp: 'A', block: { type: 1, tag: 'div' /* 省略其他屬性 */ } }, { exp: 'B', block: { type: 1, tag: 'div' /* 省略其他屬性 */ } }, { exp: 'C', block: { type: 1, tag: 'div' /* 省略其他屬性 */ } }, { exp: 'undefined', block: { type: 1, tag: 'div' /* 省略其他屬性 */ } } ] // 省略其他屬性... }
假如當前元素不滿足條件:root.if && (element.elseif || element.else) ,那么在非生產環(huán)境下會打印了警告信息。
接下來源碼:
if (currentParent && !element.forbidden) { if (element.elseif || element.else) { processIfConditions(element, currentParent); } else if (element.slotScope) { // scoped slot currentParent.plain = false; var name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element; } else { currentParent.children.push(element); element.parent = currentParent; } } if (!unary) { currentParent = element; stack.push(element); } else { closeElement(element); }
我們先從下往上講, 為什么呢?原因是在解析根元素的時候currentParent并沒有賦值。
!unary 表示解析的是非一元標簽,此時把該元素的描述對象添加到stack 棧中,并且將 currentParent 變量的值更新為當前元素的描述對象。如果一個元素是一元標簽,那么應該調用 closeElement 函數(shù)閉合該元素。
老生常談的總結:每當遇到一個非一元標簽都會將該元素的描述對象添加到stack數(shù)組,并且currentParent 始終存儲的是 stack 棧頂?shù)脑兀串斍敖馕鲈氐母讣墶?/p>
if (currentParent && !element.forbidden) { if (element.elseif || element.else) { processIfConditions(element, currentParent); } else if (element.slotScope) { // scoped slot currentParent.plain = false; var name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element; } else { currentParent.children.push(element); element.parent = currentParent; } }
這里的條件要成立,則說明當前元素存在父級( currentParent ),并且當前元素不是被禁止的元素。
常見的情況如下:
if (currentParent && !element.forbidden) { if (element.elseif || element.else) { //... } else if (element.slotScope) { // scoped slot //... } else { currentParent.children.push(element); element.parent = currentParent; } }
在 else 語句塊內,會把當前元素描述對象添加到父級元素描述對象 ( currentParent ) 的children 數(shù)組中,同時將當前元素對象的 parent 屬性指向父級元素對象,這樣就建立了元素描述對象間的父子級關系。
如果一個標簽使用 v-else-if 或 v-else 指令,那么該元素的描述對象實際上會被添加到對應的v-if 元素描述對象的 ifConditions 數(shù)組中,而非作為一個獨立的子節(jié)點,這個工作就是由如下代碼完成:
if (currentParent && !element.forbidden) { if (element.elseif || element.else) { processIfConditions(element, currentParent); } else if (element.slotScope) { // scoped slot currentParent.plain = false; var name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element; } else { //... } }
如當前解析的元素使用了 v-else-if 或 v-else 指令,則會調用 processIfConditions 函數(shù),同時將當前元素描述對象 element 和父級元素的描述對象 currentParent 作為參數(shù)傳遞:
processIfConditions 源碼
function processIfConditions(el, parent) { var prev = findPrevElement(parent.children); if (prev && prev.if) { addIfCondition(prev, { exp: el.elseif, block: el }); } else { warn$2( "v-" + (el.elseif ? ('else-if="' + el.elseif + '"') : 'else') + " " + "used on element <" + (el.tag) + "> without corresponding v-if." ); } }
findPrevElement 函數(shù)是去查找到當前元素的前一個元素描述對象,并將其賦值給 prev 常量,addIfCondition 不用多說如果prev 、prev.if 存在,調用 addIfCondition 函數(shù)在當前元素描述對象添加 ifConditions 屬性,傳入的對象存儲相關信息。
如果當前元素沒有使用 v-else-if 或 v-else 指令,下面就是判斷 slot-scope 特性,如下:
if (currentParent && !element.forbidden) { if (element.elseif || element.else) { //... } else if (element.slotScope) { // scoped slot currentParent.plain = false; var name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element; } else { //... } }
slot-scope 特性就是該元素的描述對象會被添加到父級元素的scopedSlots 對象下,這也說明使用 slot-scope 特性的元素與使用了v-else-if 或 v-else 指令的元素一樣,無法作為父級元素的子節(jié)點, slot-scope 的特性就是父級元素描述對象的 scopedSlots 對象下。
后續(xù)更多相關精彩內容,請大家多多關注。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/127761.html
vue parseHTML函數(shù)解析器遇到結束標簽,在之前文章中已講述完畢。 例如有html(template)字符串: <divid="app"> <p>{{message}}</p> </div> 產出如下: { attrs:["id="app"","id...
摘要:模板解析器原理本文來自深入淺出模板編譯原理篇的第九章,主要講述了如何將模板解析成,這一章的內容是全書最復雜且燒腦的章節(jié)。循環(huán)模板的偽代碼如下截取模板字符串并觸發(fā)鉤子函數(shù)為了方便理解,我們手動模擬解析器的解析過程。 Vue.js 模板解析器原理 本文來自《深入淺出Vue.js》模板編譯原理篇的第九章,主要講述了如何將模板解析成AST,這一章的內容是全書最復雜且燒腦的章節(jié)。本文未經排版,真...
摘要:當前正在處理的節(jié)點,以及該節(jié)點的和等信息。源碼解析之一整體分析源碼解析之三寫作中源碼解析之四寫作中作者博客作者作者微博 筆者系 vue-loader 貢獻者之一(#16) 前言 vue-loader 源碼解析系列之一,閱讀該文章之前,請大家首先參考大綱 vue-loader 源碼解析系列之 整體分析 selector 做了什么 const path = require(path) co...
我們現(xiàn)在要講述的是當解析器遇到一個文本節(jié)點時會如何為文本節(jié)點創(chuàng)建元素描述對象,那又該作何處理。 parseHTML(template,{ chars:function(){ //... }, //... }) chars源碼: chars:functionchars(text){ if(!currentParent){ { if(text===templ...
寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Compile - 源碼版 之 Parse 主要流程 本文難度較繁瑣,需要耐心觀看,如果你對 compile 源碼暫時...
知道嗎?Vue.js 有 2 個版本,一個是Runtime + Compiler版本,另一個是Runtime only版本。Runtime + Compiler版本是包含編譯代碼的,簡單來說就是Runtime only版本不包含編譯代碼的,在運行時候,需要借助 webpack 的 vue-loader 事先把模板編譯成 render 函數(shù)。 假如在你需要在客戶端編譯模板 (比如傳入一個字符串...
閱讀 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