你可能注意到,在上一步中添加的样式会选中页面上所有的 <rating-element> 以及所有的 <button>。这可能会导致样式泄漏出元素,选中你本不打算设置样式的其他节点。而你自定义元素外部的样式也可能无意中影响元素内部节点的样式。
例如,尝试在主文档的 head 中添加一个 style 标签:
<html> <head> <script src="./rating-element.js" type="module"></script> <style> span { border: 1px solid red; } </style> </head> <body> <rating-element></rating-element> </body></html> 你应该会看到评分的 span 周围有一个红色边框。这是一个简单的例子,但对于更复杂的应用程序,缺少 DOM 封装可能会导致更大的问题。这就是 Shadow DOM 发挥作用的地方。
在 connectedCallback 内部,为元素附加一个 shadow root,并将 DOM 渲染在该 root 内部:
connectedCallback() { const shadowRoot = this.attachShadow({mode: 'open'});
// 注意新的 CSS 选择器 shadowRoot.innerHTML = ` <style> :host { display: inline-flex; align-items: center; } button { background: transparent; border: none; cursor: pointer; } </style> <button class="thumb_down" > <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg> </button> <span class="rating">${this.rating}</span> <button class="thumb_up"> <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg> </button> `;} 现在红色边框应该消失了——主文档中的样式再也无法选中 shadow root 内部的节点。
你是怎么做到的?在 connectedCallback 中你调用了 this.attachShadow,它为元素附加了一个 shadow root。open 模式意味着 shadow 内容是可检查的,并且可以通过 this.shadowRoot 访问 shadow root。同时在 Chrome 检查器中查看这个 Web Component:

你现在应该能在组件内部看到一个 shadow root。你可以展开 shadow root 来查看 Shadow DOM 的内容。如果你在 Chrome 开发者工具中选中 rating 元素并调用 $0.children,你会发现它返回空。Shadow DOM 是它自己的 DOM 树,与组件的直接子节点是分离的。
一个实验:添加一个节点作为 <rating-element> 的直接子节点:
<rating-element> <div> This is the light DOM! </div></rating-element> 页面刷新后,你会看到新的 <div> 没有显示出来。这是因为 Shadow DOM 具有控制子节点(有时称为 Light DOM)如何被渲染或投射到 Shadow DOM 中的功能。
本教程不涉及 Light DOM 投射。