我的编程空间,编程开发者的网络收藏夹
学习永远不晚

vue parseHTML函数源码解析start钩子函数

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

vue parseHTML函数源码解析start钩子函数

目录
  • 正文
    • platformGetTagNamespace 源码
    •  isForbiddenTag 函数
    • addIfCondition是什么
    • processIfConditions 源码

正文

接上章节:parseHTML 函数源码解析 AST 预备知识

现在我们就可以愉快的进入到Vue start钩子函数源码部分了。

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
	
	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 钩子函数接受三个参数,这三个参数分别是标签名字 tag,该标签的属性数组attrs,以及代表着该标签是否是一元标签的标识 unary。

接下来别害怕看不懂,我们一点点来分析它函数体中的代码。

var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);

开头定义了 ns 变量,它的值为标签的命名空间,如何获取当前元素的命名空间呢?首先检测currentParent 变量是否存在,我们知道 currentParent 变量为当前元素的父级元素描述对象,如果当前元素存在父级并且父级元素存在命名空间,则使用父级的命名空间作为当前元素的命名空间。

如果父级元素不存在或父级元素没有命名空间那么会调用platformGetTagNamespace函数,platformGetTagNamespace 函数只会获取 svg 和 math 这两个标签的命名空间,但这两个标签的所有子标签都会继承它们两个的命名空间。

platformGetTagNamespace 源码

function getTagNamespace(tag) {
	if (isSVG(tag)) {
		return "svg"
	}
	if (tag === "math") {
		return "math"
	}
}

接下来源码:

if (isIE && ns === "svg") {
	attrs = guardIESVGBug(attrs);
}

这里通过isIE来判断宿主环境是不是IE浏览器,并且前元素的命名空间为svg, 如果是通过guardIESVGBug处理当前元素的属性数组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 函数就是用来修改NS1:xmlns:feature属性并移除xmlns:NS1="" 属性的。

接下来源码:

var element = createASTElement(tag, attrs, currentParent);
if (ns) {
	element.ns = ns;
}

在上章节聊过,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 函数

function isForbiddenTag(el) {
	return (
		el.tag === 'style' ||
		(el.tag === 'script' &amp;&amp; (
			!el.attrsMap.type ||
			el.attrsMap.type === 'text/javascript'
		))
	)
}

可以看到,style,script 都是在禁止名单中,但通过isForbiddenTag 也发现一个彩蛋。

<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 循环遍历了preTransforms 数组,preTransforms 是通过pluckModuleFunction 函数从options.modules 选项中筛选出名字为preTransformNode 函数所组成的数组。实际上 preTransforms 数组中只有一个 preTransformNode 函数该函数只用来处理 input 标签我们在后面章节会来讲它。

接下来源码:

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*的函数,这些函数是做什么用的呢?实际上process* 系列函数的作用就是对元素描述对象做进一步处理,比如其中一个函数叫做 processPre,这个函数的作用就是用来检测元素是否拥有v-pre 属性,如果有v-pre 属性则会在 element 描述对象上添加一个 pre 属性,如下:

{
  type: 1,
  tag,
  attrsList: attrs,
  attrsMap: makeAttrsMap(attrs),
  parent,
  children: [],
  pre: true
}

总结:所有process* 系列函数的作用都是为了让一个元素的描述对象更加充实,使这个对象能更加详细地描述一个元素, 不过我们本节主要总结解析一个开始标签需要做的事情,所以稍后去看这些代码的实现。

接下来源码:

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 函数内部首先通过判断 el.tag === 'slot' || el.tag === 'template' 来判断根元素是否是slot 标签或 template 标签,如果是则打印警告信息。接着又判断当前元素是否使用了 v-for 指令,因为v-for 指令会渲染多个节点所以根元素是不允许使用 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 &amp;&amp; (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函数检查根元素是否符合要求。

再来看 else if 语句的条件,当 stack 为空的情况下会执行 else if 语句块内的代码, 那stack 什么情况下才为空呢?前面已经多次提到每当遇到一个非一元标签时就会将该标签的描述对象放进数组,并且每当遇到一个结束标签时都会将该标签的描述对象从 stack 数组中拿掉,那也就是说在只有一个根元素的情况下,正常解析完成一段 html 代码后 stack 数组应该为空,或者换个说法,即当 stack 数组被清空后则说明整个模板字符串已经解析完毕了,但此时 start 钩子函数仍然被调用了,这说明模板中存在多个根元素,这时 else if 语句块内的代码将被执行:

接下来源码:

if (root.if &amp;&amp; (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 函数处理元素描述对象时,如果发现元素的属性中有 v-if 或 v-else-if 或 v-else ,则会在元素描述对象上添加相应的属性作为标识。

上面代码如果第一个根元素上有 .if 的属性,而非第一个根元素 element 有 .elseif 属性或者 .else 属性,这说明根元素都是由 v-if、v-else-if、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."
	);
}

checkRootConstraints 函数检查当前元素是否符合作为根元素的要求,这都能理解。

addIfCondition是什么

看下它的源代码。

function addIfCondition(el, condition) {
	if (!el.ifConditions) {
		el.ifConditions = [];
	}
	el.ifConditions.push(condition);
}

代码很简单,调用addIfCondition 传递的参数 root 对象,在函数体中扩展一个属性addIfCondition, root.addIfCondition 属性值是一个对象。 此对象中有两个属性exp、block。实际上该函数是一个通用的函数,不仅仅用在根元素中,它用在任何由 v-if、v-else-if 以及 v-else 组成的条件渲染的模板中。

通过如上分析我们可以发现,具有 v-else-if 或 v-else 属性的元素的描述对象会被添加到具有 v-if 属性的元素描述对象的 .ifConnditions 数组中。

举个例子,如下模板:

<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) ,那么在非生产环境下会打印了警告信息。

接下来源码:

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 函数闭合该元素。

老生常谈的总结:每当遇到一个非一元标签都会将该元素的描述对象添加到stack数组,并且currentParent 始终存储的是 stack 栈顶的元素,即当前解析元素的父级。

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 数组中,同时将当前元素对象的 parent 属性指向父级元素对象,这样就建立了元素描述对象间的父子级关系。

如果一个标签使用 v-else-if 或 v-else 指令,那么该元素的描述对象实际上会被添加到对应的v-if 元素描述对象的 ifConditions 数组中,而非作为一个独立的子节点,这个工作就是由如下代码完成:

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 函数,同时将当前元素描述对象 element 和父级元素的描述对象 currentParent 作为参数传递:

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 函数是去查找到当前元素的前一个元素描述对象,并将其赋值给 prev 常量,addIfCondition 不用多说如果prev 、prev.if 存在,调用 addIfCondition 函数在当前元素描述对象添加 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 指令的元素一样,他们都不会作为父级元素的子节点,对于使用了 slot-scope 特性的元素来讲它们将被添加到父级元素描述对象的 scopedSlots 对象下。

自 2.6.0 起有所更新。已废弃的使用slot-scope 特性的语法在这里。所以此块内容就不铺开来讲了,有兴趣的同学可以去了解下,更多关于vue parseHTML start钩子函数的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

vue parseHTML函数源码解析start钩子函数

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

vue parseHTML函数源码分析start钩子函数

这篇文章主要讲解了“vue parseHTML函数源码分析start钩子函数”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue parseHTML函数源码分析start钩子函数”吧!正文现
2023-07-02

vue parseHTML函数源码分析

本文小编为大家详细介绍“vue parseHTML函数源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“vue parseHTML函数源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。正文Vue编译器源
2023-07-02

vue parseHTML函数源码分析AST

这篇“vue parseHTML函数源码分析AST”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue parseHTML函
2023-07-02

Vue八大生命周期钩子函数源码分析

本篇内容主要讲解“Vue八大生命周期钩子函数源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue八大生命周期钩子函数源码分析”吧!一.速识概念:我们把一个对象从生成(new)到被销毁(d
2023-07-05

python 钩子函数详解

1.with 与 __enter__ ,__exit__ with obj: expressment #进入with块时调用 obj.__enter__() #退出with块时调用 obj.__exit__(
2023-01-31

drupal之hook_link和hook_link_alter钩子函数解析

本文实例讲述了drupal中hook_link和hook_link_alter钩子函数的用法。分享给大家供大家参考。具体如下: 在Drupal中有个钩子,叫做hook_link,参数如下:复制代码代码如下:hook_link($type,
2022-06-12

怎么自定义Vue钩子函数

这篇文章主要讲解了“怎么自定义Vue钩子函数”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么自定义Vue钩子函数”吧!useWindowResize这是一个基本的钩子,因为它被用在很多项目
2023-06-29

vue钩子函数的作用是什么

Vue钩子函数的作用是在组件生命周期的不同阶段执行特定的代码逻辑。它们使开发者能够在组件的不同生命周期阶段进行自定义操作,以满足不同的需求。常用的Vue钩子函数包括:1. beforeCreate:在实例初始化之后,数据观测之前被调用。可以
2023-08-08

Vue中callHook钩子函数怎么调用

这篇“Vue中callHook钩子函数怎么调用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue中callHook钩子函数
2023-07-04

编程热搜

目录