Shadow DOM 必须通过 attachShadow() 创建,宿主元素需支持且仅能调用一次;样式隔离依赖 :host 和 ::slotted;DOM 查询须用 shadowRoot.querySelector();布局需在 Shadow DOM 内定义。

Shadow DOM 的创建必须用 attachShadow(),不能靠 HTML 属性
很多人误以为在 HTML 中加个 shadowroot 属性或类似写法就能启用 Shadow DOM,实际完全不行。Shadow DOM 只能通过 JavaScript 调用 attachShadow() 方法挂载,且宿主元素必须是自定义元素(如 my-card)或部分原生容器(如 div、span),但不包括 img、input 等替换元素。
常见错误现象:Failed to execute 'attachShadow' on 'Element': This element does not support shadow roots —— 就是因为选了不支持的宿主元素。
-
attachShadow()必须传入{ mode: 'open' }或{ mode: 'closed' },不传会直接报错 -
mode: 'closed'时,外部 JS 无法通过element.shadowRoot访问,调试困难,日常开发推荐用'open' - 同一个元素只能调用一次
attachShadow(),重复调用会抛InvalidStateError
样式隔离不是自动的,:host 和 ::slotted 是关键入口
Shadow DOM 内部样式默认不会泄露出去,外部样式也进不来 —— 但这不意味着你不用写样式。真正控制布局和外观的,是三个核心伪类:
-
:host:匹配宿主元素自身,比如宿主是,那么:host { display: inline-block; }就作用于该标签 -
:host(.primary):可响应宿主上的 class,实现主题切换 -
::slotted(*):匹配所有被投影进来的内容,常用来重置子元素 margin/padding,避免布局塌陷
注意: 本身不渲染,只是内容出口;若没写 ,投进去的子节点会被丢弃(除非用 + slot="xxx" 显式匹配)。
立即学习“前端免费学习笔记(深入)”;
DOM 查询受限,shadowRoot.querySelector() 是唯一可靠路径
Shadow DOM 天然隔离,document.querySelector() 找不到内部节点,element.children 也只返回 slot 外层的“壳”,看不到影子树里的真实结构。
正确做法是明确区分作用域:
- 查宿主内 Shadow DOM 中的按钮:
host.shadowRoot.querySelector('button') - 查 slot 里被投影的标题:
host.shadowRoot.querySelector('h2')(前提是 h2 确实被 slot 渲染出来了) - 想监听内部按钮点击?事件必须在
shadowRoot上绑定,或利用事件冒泡 +event.composedPath()捕获
容易踩的坑:host.querySelector('button') 返回 null,不是 bug,是设计如此。
与 Flex/Grid 布局共存没问题,但容器必须在 Shadow DOM 内部
Shadow DOM 不影响 CSS 布局能力,display: flex 和 display: grid 在 shadowRoot 里照常工作。问题出在「谁是容器」—— 如果把 display: grid 写在宿主元素上,它只布局 slot 占位符,不是真正的子内容。
典型错误写法:
标题内容
上面的 .grid-container 生效对象是 自身,不是其内部结构。正确方式是在 Shadow DOM 模板里写:
const template = document.createElement('template');
template.innerHTML = `
`;
复杂点在于:每个自定义元素都要自己管理模板、样式、事件,没有全局样式穿透,也没有隐式继承。这既是隔离性保障,也是开发成本所在。










