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

正文

接上章节: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
	/* 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 钩子函数接受三个参数,这三个参数分别是标签名字 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标签:

被渲染为:

标签中多了 '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' && (
			!el.attrsMap.type ||
			el.attrsMap.type === 'text/javascript'
		))
	)
}

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

当定义模板的方式如上,在

你可能感兴趣的:(vue parseHTML函数源码解析start钩子函数)