这里所说的Web组件指的是一套用于增强DOM行为的工具,包括影子DOM、自定义元素和HTML模板。
20.11.1 HTML模板
在Web组件之前,一直缺少基于HTML解析构建DOM子树,然后在需要时再把这个子树渲染出来的机制。 在浏览器中渲染时,上面例子中的文本不会被渲染到页面上。 标签。 存在于一个包含在HTML模板中的DocumentFragment节点内。 通过元素的content属性可以取得这个DocumentFragment的引用:
此时DocumentFragment就像一个对应子树的最小化document对象。 DocumentFragment也是批量向HTML中添加元素的高效工具。 注意,在前面的例子中,DocumentFragment的所有子节点都高效地转移到了foo元素上,转移之后DocumentFragment变空了。同样的过程也可以使用标签重现 脚本执行可以推迟到将DocumentFragment的内容实际添加到DOM树。 概念上讲,影子DOM(shadow DOM)Web组件相当直观,通过它可以将一个完整的DOM树作为节点添加到父DOM树。这样可以实现DOM封装,意味着CSS样式和CSS选择符可以限制在影子DOM子树而不是整个顶级DOM树中。 假设有以下HTML标记,其中包含多个类似的DOM子树: 这3个DOM子树会分别渲染为不同的颜色,常规情况下,为了给每个子树应用唯一的样式,又不使用style属性,就需要给每个子树添加一个唯一的类名,然后通过相应的选择符为它们添加样式 考虑到安全及避免影子DOM冲突,并非所有元素都可以包含影子DOM。
一种间接方案是使用innerHTML把标记字符串转换为DOM元素,但这种方式存在严重的安全隐患。
另一种间接方案是使用document.createElement()构建每个元素,然后逐个把它们添加到孤儿根节点(不是添加到DOM),但这样做特别麻烦,完全与标记无关。
相反,更好的方式是:提前在页面中写出特殊标记,让浏览器自动将其解析为DOM子树,但跳过渲染。
这正是HTML模板的核心思想,而标签正是为这个目的而生的。
<template id="foo">
<p>我在一个模板里面</p>
</template>
1.使用DocumentFragment
因为的内容不属于活动文档,所以document.querySelector()等DOM查询方法,不会发现其中的
这是因为
在浏览器中通过开发者工具检查网页内容时,可以看到中的DocumentFragment:
<template id="foo">
#document-fragment
<p>我在一个模板里面</p>
</template>
console.log(document.querySelector('#foo').content);
// #document-fragment
换句话说,DocumentFragment上的DOM匹配方法可以查询其子树中的节点:const fragment = document.querySelector('#foo').content;
console.log(document.querySelector('p')); // null
console.log(fragment.querySelector('p')); // <p>我在一个模板里面</p>
比如,我们想以最快的方式给某个HTML元素添加多个子元素。如果连续调用document.appendChild(),则不仅费事,还会导致多次布局重排。
而使用DocumentFragment可以一次性添加所有子节点,最多只会有一次布局重排:<!-- 开始状态 -->
<div id="foo"></div>
<!-- 期待的最终状态 -->
<!--
<div id="foo">
<p></p>
<p></p>
<p></p>
</div>
-->
<script>
// 也可使用document.createDocumentFragment()
const fragment = new DocumentFragment();
const foo = document.querySelector('#foo');
// 为DocumentFragment添加子元素不会导致布局重排
fragment.appendChild(document.createElement('p'));
fragment.appendChild(document.createElement('p'));
fragment.appendChild(document.createElement('p'));
console.log(fragment.children.length); // 3
foo.appendChild(fragment);
console.log(fragment.children.length); // 0
console.log(document.body.innerHTML); // <div id="foo"><p></p><p></p><p></p></div>
</script>
2.使用标签
如果想要复制模板,可以使用importNode()方法克隆DocumentFragment
3.模板脚本
如果新添加的元素需要进行某些初始化,这种延迟执行是有用的。
20.11.2 影子DOM
影子DOM与HTML模板还是有区别的,主要表现在影子DOM的内容会实际渲染到页面上,而HTML模板的内容不会。
1.理解影子DOM
<div>
<p>Make me red!</p>
<p>Make me blue!</p>
<p>Make me green!</p>
</div>
理想情况下,应该能够把CSS限制在使用它们的DOM上:这正是影子DOM最初的使用场景。
2.创建影子DOM
尝试给无效元素或者已经有了影子DOM的元素添加影子DOM会导致抛出错误。
以下是可以容纳影子DOM的元素:
❑ 任何以有效名称创建的自定义元素(参见HTML规范中相关的定义)❑
