最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue?parseHTML?函數(shù)源碼解析

 更新時間:2022年07月13日 16:51:40   作者:李李  
這篇文章主要為大家介紹了vue?parseHTML函數(shù)的源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

接上篇:

Vue編譯器源碼分析AST 抽象語法樹

function parseHTML(html, options) {
	var stack = [];
	var expectHTML = options.expectHTML;
	var isUnaryTag$$1 = options.isUnaryTag || no;
	var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
	var index = 0;
	var last, lastTag;
	// 開啟一個 while 循環(huán),循環(huán)結(jié)束的條件是 html 為空,即 html 被 parse 完畢
	while (html) {
		last = html;
		if (!lastTag || !isPlainTextElement(lastTag)) {
			// 確保即將 parse 的內(nèi)容不是在純文本標簽里 (script,style,textarea)
		} else {
			// parse 的內(nèi)容是在純文本標簽里 (script,style,textarea)
		}
		//將整個字符串作為文本對待
		if (html === last) {
			options.chars && options.chars(html);
			if (!stack.length && options.warn) {
				options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));
			}
			break
		}
	}
	// Clean up any remaining tags
	parseEndTag();
	function advance(n) {
		index += n;
		html = html.substring(n);
	}
	//parse 開始標簽
	function parseStartTag() {
		//...
	}
	//處理 parseStartTag 的結(jié)果
	function handleStartTag(match) {
		//...
	}
	//parse 結(jié)束標簽
	function parseEndTag(tagName, start, end) {
		//...
	}
}

可以看到 parseHTML 函數(shù)接收兩個參數(shù):html 和 options ,其中 html 是要被編譯的字符串,而options則是編譯器所需的選項。

整體上來講 parseHTML分為三部分。

  • 函數(shù)開頭定義的一些常量和變量
  • while 循環(huán)
  • parse 過程中需要用到的 analytic function

函數(shù)開頭定義的一些常量和變量

先從第一部分開始講起

var stack = [];
var expectHTML = options.expectHTML;
var isUnaryTag$$1 = options.isUnaryTag || no;
var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
var index = 0;
var last, lastTag;

第一個變量是 stack,它被初始化為一個空數(shù)組,在 while 循環(huán)中處理 html 字符流的時候每當遇到一個非單標簽,都會將該開始標簽 push 到該數(shù)組。它的作用模板中 DOM 結(jié)構(gòu)規(guī)范性的檢測。

但在一個 html 字符串中,如何判斷一個非單標簽是否缺少結(jié)束標簽呢?

假設(shè)我們有如下html字符串:

<div><p><span></p></div>

在編譯這個字符串的時候,首先會遇到 div 開始標簽,并將該 push 到 stack 數(shù)組,然后會遇到 p 開始標簽,并將該標簽 push 到 stack ,接下來會遇到 span 開始標簽,同樣被 push 到 stack ,此時 stack 數(shù)組內(nèi)包含三個元素。

再然后便會遇到 p 結(jié)束標簽,按照正常邏輯可以推理出最先遇到的結(jié)束標簽,其對應(yīng)的開始標簽應(yīng)該最后被push到 stack 中,也就是說 stack 棧頂?shù)脑貞?yīng)該是 span ,如果不是 span 而是 p,這說明 span 元素缺少閉合標簽。

這就是檢測 html 字符串中是否缺少閉合標簽的原理。

第二個變量是 expectHTML,它的值被初始化為 options.expectHTML,也就是編譯器選項中的 expectHTML。

第三個常量是 isUnaryTag,用來檢測一個標簽是否是一元標簽。

第四個常量是 canBeLeftOpenTag,用來檢測一個標簽是否是可以省略閉合標簽的非一元標簽。

  • index 初始化為 0 ,標識著當前字符流的讀入位置。
  • last 存儲剩余還未編譯的 html 字符串。
  • lastTag 始終存儲著位于 stack 棧頂?shù)脑亍?/li>

while 循環(huán)

接下來將進入第二部分,即開啟一個 while 循環(huán),循環(huán)的終止條件是 html 字符串為空,即html 字符串全部編譯完畢。

while (html) {
	last = html;
	// Make sure we're not in a plaintext content element like script/style
	if (!lastTag || !isPlainTextElement(lastTag)) {
		var textEnd = html.indexOf('<');
		if (textEnd === 0) {
			// Comment:
			if (comment.test(html)) {
				var commentEnd = html.indexOf('-->');
				if (commentEnd >= 0) {
					if (options.shouldKeepComment) {
						options.comment(html.substring(4, commentEnd));
					}
					advance(commentEnd + 3);
					continue
				}
			}
			// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
			if (conditionalComment.test(html)) {
				var conditionalEnd = html.indexOf(']>');
				if (conditionalEnd >= 0) {
					advance(conditionalEnd + 2);
					continue
				}
			}
			// Doctype:
			var doctypeMatch = html.match(doctype);
			if (doctypeMatch) {
				advance(doctypeMatch[0].length);
				continue
			}
			// End tag:
			var endTagMatch = html.match(endTag);
			if (endTagMatch) {
				var curIndex = index;
				advance(endTagMatch[0].length);
				parseEndTag(endTagMatch[1], curIndex, index);
				continue
			}
			// Start tag:
			var startTagMatch = parseStartTag();
			if (startTagMatch) {
				handleStartTag(startTagMatch);
				if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
					advance(1);
				}
				continue
			}
		}
		var text = (void 0),
			rest = (void 0),
			next = (void 0);
		if (textEnd >= 0) {
			rest = html.slice(textEnd);
			while (
				!endTag.test(rest) &&
				!startTagOpen.test(rest) &&
				!comment.test(rest) &&
				!conditionalComment.test(rest)
			) {
				// < in plain text, be forgiving and treat it as text
				next = rest.indexOf('<', 1);
				if (next < 0) {
					break
				}
				textEnd += next;
				rest = html.slice(textEnd);
			}
			text = html.substring(0, textEnd);
			advance(textEnd);
		}
		if (textEnd < 0) {
			text = html;
			html = '';
		}
		if (options.chars && text) {
			options.chars(text);
		}
	} else {
		var endTagLength = 0;
		var stackedTag = lastTag.toLowerCase();
		var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag +
			'[^>]*>)', 'i'));
		var rest$1 = html.replace(reStackedTag, function(all, text, endTag) {
			endTagLength = endTag.length;
			if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
				text = text
					.replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
					.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
			}
			if (shouldIgnoreFirstNewline(stackedTag, text)) {
				text = text.slice(1);
			}
			if (options.chars) {
				options.chars(text);
			}
			return ''
		});
		index += html.length - rest$1.length;
		html = rest$1;
		parseEndTag(stackedTag, index - endTagLength, index);
	}
	if (html === last) {
		options.chars && options.chars(html);
		if (!stack.length && options.warn) {
			options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));
		}
		break
	}
}

首先將在每次循環(huán)開始時將 html 的值賦給變量 last :

last = html;

為什么這么做?在 while 循環(huán)即將結(jié)束的時候,有一個對 last 和 html 這兩個變量的比較,在此可以找到答案:

if (html === last) {}

如果兩者相等,則說明html 在經(jīng)歷循環(huán)體的代碼之后沒有任何改變,此時會"Mal-formatted tag at end of template: \"" + html + "\"" 錯誤信息提示。

接下來可以簡單看下整體while循環(huán)的結(jié)構(gòu)。

while (html) {
  last = html
  if (!lastTag || !isPlainTextElement(lastTag)) {
    // parse 的內(nèi)容不是在純文本標簽里
  } else {
    // parse 的內(nèi)容是在純文本標簽里 (script,style,textarea)
  }
  // 極端情況下的處理
  if (html === last) {
    options.chars && options.chars(html)
    if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {
      options.warn(`Mal-formatted tag at end of template: "${html}"`)
    }
    break
  }
}

接下來我們重點來分析這個if else 中的代碼。

!lastTag || !isPlainTextElement(lastTag)

lastTag 剛剛講到它會一直存儲 stack 棧頂?shù)脑兀钱斁幾g器剛開始工作時,他只是一個空數(shù)組對象,![] == false

isPlainTextElement(lastTag) 檢測 lastTag 是否為純標簽內(nèi)容。

var isPlainTextElement = makeMap('script,style,textarea', true);

lastTag 為空數(shù)組 ,isPlainTextElement(lastTag ) 返回false, !isPlainTextElement(lastTag) ==true, 有興趣的同學可以閱讀下 makeMap 源碼。

接下來我們繼續(xù)往下看,簡化版的代碼。

if (!lastTag || !isPlainTextElement(lastTag)) {
  var textEnd = html.indexOf('<')
  if (textEnd === 0) {
    // 第一個字符就是(<)尖括號
  }
 var text = (void 0),
     rest = (void 0),
     next = (void 0);
  if (textEnd >= 0) {
    //第一個字符不是(<)尖括號
  }
  if (textEnd < 0) {
    // 第一個字符不是(<)尖括號
  }
  if (options.chars && text) {
    options.chars(text)
  }
} else {
  // 省略 ...
}

textEnd ===0

當 textEnd === 0 時,說明 html 字符串的第一個字符就是左尖括號,比如 html 字符串為:<div>box</div>,那么這個字符串的第一個字符就是左尖括號(<)。

if (textEnd === 0) {
	// Comment: 如果是注釋節(jié)點
	if (comment.test(html)) {
		var commentEnd = html.indexOf('-->');
		if (commentEnd >= 0) {
			if (options.shouldKeepComment) {
				options.comment(html.substring(4, commentEnd));
			}
			advance(commentEnd + 3);
			continue
		}
	}
	//如果是條件注釋節(jié)點
	if (conditionalComment.test(html)) {
		var conditionalEnd = html.indexOf(']>');
		if (conditionalEnd >= 0) {
			advance(conditionalEnd + 2);
			continue
		}
	}
	// 如果是 Doctyp節(jié)點 
	var doctypeMatch = html.match(doctype);
	if (doctypeMatch) {
		advance(doctypeMatch[0].length);
		continue
	}
	// End tag:  結(jié)束標簽
	var endTagMatch = html.match(endTag);
	if (endTagMatch) {
		var curIndex = index;
		advance(endTagMatch[0].length);
		parseEndTag(endTagMatch[1], curIndex, index);
		continue
	}
	// Start tag: 開始標簽
	var startTagMatch = parseStartTag();
	if (startTagMatch) {
		handleStartTag(startTagMatch);
		if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
			advance(1);
		}
		continue
	}
}

細枝末節(jié)我們不看,重點在End tag 、 Start tag 上。

我們先從解析標簽開始分析

var startTagMatch = parseStartTag();
if (startTagMatch) {
	handleStartTag(startTagMatch);
	if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
		advance(1);
	}
	continue
}

parseStartTag 函數(shù)解析開始標簽

解析開始標簽會調(diào)用parseStartTag函數(shù),如果有返回值,說明開始標簽解析成功。

function parseStartTag() {
	var start = html.match(startTagOpen);
	if (start) {
		var match = {
			tagName: start[1],
			attrs: [],
			start: index
		};
		advance(start[0].length);
		var end, attr;
		while (!(end = html.match(startTagClose)) &amp;&amp; (attr = html.match(attribute))) {
			advance(attr[0].length);
			match.attrs.push(attr);
		}
		if (end) {
			match.unarySlash = end[1];
			advance(end[0].length);
			match.end = index;
			return match
		}
	}
}

parseStartTag 函數(shù)首先會調(diào)用 html 字符串的 match 函數(shù)匹配 startTagOpen 正則,前面我們分析過編譯器所需的正則。

Vue編譯器token解析規(guī)則-正則分析

如果匹配成功,那么start 將是一個包含兩個元素的數(shù)組:第一個元素是標簽的開始部分(包含< 和 標簽名稱);第二個元素是捕獲組捕獲到的標簽名稱。比如有如下template:

<div></div>

start為:

start = ['&lt;div', 'div']

接下來:

定義了 match 變量,它是一個對象,初始狀態(tài)下?lián)碛腥齻€屬性:

  • tagName:它的值為 start[1] 即標簽的名稱。
  • attrs :這個數(shù)組就是用來存儲將來被匹配到的屬性。
  • start:初始值為 index,是當前字符流讀入位置在整個 html 字符串中的相對位置。
advance(start[0].length);

相對就比較簡單了,他的作用就是在源字符中截取已經(jīng)編譯完成的字符,我們知道當html 字符為 “”,整個詞法分析的工作就結(jié)束了,在這中間扮演重要角色的就是advance方法。

function advance(n) {
	index += n;
	html = html.substring(n);
}

接下來:

var end, attr;
while (!(end = html.match(startTagClose)) &amp;&amp; (attr = html.match(attribute))) {
	advance(attr[0].length);
	match.attrs.push(attr);
}
if (end) {
	match.unarySlash = end[1];
	advance(end[0].length);
	match.end = index;
	return match
  }
}

主要看while循環(huán),循環(huán)的條件有兩個,第一個條件是:沒有匹配到開始標簽的結(jié)束部分,這個條件的實現(xiàn)方式主要使用了 startTagClose 正則,并將結(jié)果保存到 end 變量中。

第二個條件是:匹配到了屬性,主要使用了attribute正則。

總結(jié)下這個while循環(huán)成立要素:沒有匹配到開始標簽的結(jié)束部分,并且匹配到了開始標簽中的屬性,這個時候循環(huán)體將被執(zhí)行,直到遇到開始標簽的結(jié)束部分為止。

接下來在循環(huán)體內(nèi)做了兩件事,首先調(diào)用advance函數(shù),參數(shù)為attr[0].length即整個屬性的長度。然后會將此次循環(huán)匹配到的結(jié)果push到前面定義的match對象的attrs數(shù)組中。

advance(attr[0].length);
match.attrs.push(attr);

接下來看下最后這部分代碼。

if (end) {
	match.unarySlash = end[1];
	advance(end[0].length);
	match.end = index;
	return match
}

首先判斷了變量 end 是否為真,我們知道,即使匹配到了開始標簽的開始部分以及屬性部分但是卻沒有匹配到開始標簽的結(jié)束部分,這說明這根本就不是一個開始標簽。所以只有當變量end存在,即匹配到了開始標簽的結(jié)束部分時,才能說明這是一個完整的開始標簽。

如果變量end的確存在,那么將會執(zhí)行 if 語句塊內(nèi)的代碼,不過我們需要先了解一下變量end的值是什么?

比如當html(template)字符串如下時:

<br />

那么匹配到的end的值為:

end = ['/>', '/']

比如當html(template)字符串如下時:

<div></div>

那么匹配到的end的值為:

end = ['>', undefined]

結(jié)論如果end[1]不為undefined,那么說明該標簽是一個一元標簽。

那么現(xiàn)在再看if語句塊內(nèi)的代碼,將很容易理解,首先在match對象上添加unarySlash屬性,其值為end[1]

match.unarySlash = end[1];

然后調(diào)用advance函數(shù),參數(shù)為end[0].length,接著在match 對象上添加了一個end屬性,它的值為index,注意由于先調(diào)用的advance函數(shù),所以此時的index已經(jīng)被更新了。最后將match 對象作為 parseStartTag 函數(shù)的返回值返回。

只有當變量end存在時,即能夠確定確實解析到了一個開始標簽的時候parseStartTag函數(shù)才會有返回值,并且返回值是match對象,其他情況下parseStartTag全部返回undefined。

總結(jié):

我們模擬假設(shè)有如下html(template)字符串:

<div id="box" v-if="watings"></div>

則parseStartTag函數(shù)的返回值如下:

match = {
  tagName: 'div',
  attrs: [
    [
      'id="box"',
      'id',
      '=',
      'box',
      undefined,
      undefined
    ],
    [
      ' v-if="watings"',
      'v-if',
      '=',
      'watings',
      undefined,
      undefined
    ]
  ],
  start: index,
  unarySlash: undefined,
  end: index
}

我們講解完了parseStartTag函數(shù)及其返回值,現(xiàn)在我們回到對開始標簽的 parse 部分,接下來我們會繼續(xù)講解,拿到返回值之后的處理。

var startTagMatch = parseStartTag();
if (startTagMatch) {
	handleStartTag(startTagMatch);
	if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
		advance(1);
	}
	continue
}

篇幅有限請移步:

parseHTML 函數(shù)源碼解析返回值后的處理

以上就是vue parseHTML 函數(shù)源碼解析的詳細內(nèi)容,更多關(guān)于vue parseHTML函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue之定義全局工具類問題

    Vue之定義全局工具類問題

    這篇文章主要介紹了Vue之定義全局工具類問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • vue計算屬性+vue中class與style綁定(推薦)

    vue計算屬性+vue中class與style綁定(推薦)

    這篇文章主要介紹了vue計算屬性+vue中class與style綁定,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • vue-cli history模式實現(xiàn)tomcat部署報404的解決方式

    vue-cli history模式實現(xiàn)tomcat部署報404的解決方式

    這篇文章主要介紹了vue-cli history模式實現(xiàn)tomcat部署報404的解決方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-09-09
  • Vue?el-table實現(xiàn)右鍵菜單功能

    Vue?el-table實現(xiàn)右鍵菜單功能

    這篇文章主要為大家詳細介紹了Vue?el-table實現(xiàn)右鍵菜單功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 詳解Vue源碼學習之雙向綁定

    詳解Vue源碼學習之雙向綁定

    這篇文章主要介紹了Vue源碼學習之雙向綁定,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • Vue3?中實現(xiàn)元素拖拽功能

    Vue3?中實現(xiàn)元素拖拽功能

    這篇文章主要介紹了在Vue3中實現(xiàn)飄逸的元素拖拽,在本次案例中需要認真思考對應(yīng)的幾個坐標和移動時坐標如何更新,事件的使用要成對出現(xiàn),如何在這個拖拽的 Icon 上增加點擊事件時還需要多做一些處理,需要的朋友可以參考下
    2023-07-07
  • Vue項目中在父組件中直接調(diào)用子組件的方法

    Vue項目中在父組件中直接調(diào)用子組件的方法

    這篇文章主要給大家介紹了Vue項目中如何在父組件中直接調(diào)用子組件的方法,文章通過代碼示例介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2023-11-11
  • Vue數(shù)據(jù)雙向綁定原理實例解析

    Vue數(shù)據(jù)雙向綁定原理實例解析

    這篇文章主要介紹了Vue數(shù)據(jù)雙向綁定原理實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-05-05
  • vue3.0手動封裝分頁組件的方法

    vue3.0手動封裝分頁組件的方法

    這篇文章主要為大家詳細介紹了vue3.0手動封裝分頁組件的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Vue下拉選擇框Select組件使用詳解(一)

    Vue下拉選擇框Select組件使用詳解(一)

    這篇文章主要為大家詳細介紹了Vue下拉選擇框Select組件的使用,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評論

霍州市| 喀喇沁旗| 天台县| 延吉市| 庆安县| 渑池县| 井陉县| 乐清市| 颍上县| 连江县| 唐河县| 霍州市| 贵阳市| 大方县| 惠来县| 呼伦贝尔市| 陆河县| 青川县| 昭通市| 古蔺县| 张掖市| 凤城市| 中阳县| 宝鸡市| 平江县| 海阳市| 临汾市| 海南省| 孟州市| 仁寿县| 河北区| 双辽市| 高青县| 漳州市| 东安县| 林芝县| 泗洪县| 昌邑市| 云安县| 延津县| 肥东县|