web components通过custom elements和shadow dom实现真正隔离:必须在connectedcallback中调用attachshadow({mode:'open'}),样式与结构须封装于shadowroot内,禁止外部css注入和dom硬编码访问。

不能靠“把 JS 写在外部文件”就叫完全隔离——那只是物理分离,不是运行时隔离。真正能切断视图与逻辑耦合的,是让 HTML 元素不持有任何可被 JS 直接篡改的引用路径,且行为触发不依赖 DOM 层级或 class 名硬编码。
customElements.define() 是唯一能建立天然边界的方式
浏览器原生支持的自定义元素机制,会在解析时自动创建独立作用域,连 querySelector 都无法跨过 shadow boundary 匹配内部节点:
-
customElements.define("my-card", class extends HTMLElement { ... })注册后,<my-card></my-card>就是一个语义完整、可封装、可复用的单元 - 必须在
connectedCallback()中调用this.attachShadow({ mode: "open" }),否则所有样式和结构仍在全局 DOM 树中,毫无隔离可言 - 不要在
constructor()里操作this.shadowRoot—— 此时它已存在,但 DOM 尚未挂载,querySelector必然返回null
HTML 模板不能含内联事件或 data 属性透传逻辑
写 <button onclick="handleClick()"></button> 或 <div data-action="submit"> 本质是把行为契约写死在 HTML 字符串里,JS 仍需主动识别并绑定,等于留了后门。
<ul>
<li>所有交互入口必须由组件类内部声明:在 <code>shadowRoot 中创建按钮后,立即用 addEventListener 绑定,不暴露任何可被外部 JS 扫描的属性
onxxx 属性和 data- 行为标记;若需配置,统一走 attributeChangedCallback + observedAttributes 声明<my-card disabled></my-card>,而不是 <my-card data-disabled="true"></my-card>
样式与结构必须共存于 shadowRoot 内部
只要 <style></style> 或 <link rel="stylesheet"> 出现在 shadowRoot 外,隔离就失效了。外部 CSS 会穿透,内部样式也可能意外泄漏。
- 禁止向
document.head动态插入<link>加载 Foundation/Font Awesome 等第三方样式 —— 它们会污染全局 - 正确做法:把第三方 CSS 文本提取出来,用
CSSStyleSheet实例 +adoptedStyleSheets注入到shadowRoot(注意 Safari 16.4+、Chrome 73+ 才稳定支持) - 避免使用
::slotted(*)重置 slot 内容样式 —— 这等于主动开放布局控制权,破坏封装边界
状态更新必须驱动 DOM 重建,而非局部 patch
很多开发者以为 element.classList.add("active") 或 el.innerHTML = newHtml 就是响应式,其实这只是手动同步,极易漏掉状态分支或产生竞态。
- 应在组件类中维护单一状态源(如
this._isOpen = true),所有 UI 变更都由this._render()方法统一生成完整 DOM 片段 -
_render()应每次清空shadowRoot再重新appendChild,避免残留节点干扰后续逻辑 - 不要在事件回调里直接改
style.color或setAttribute("class", ...)—— 这会让视图层再次承担逻辑判断职责
最难被意识到的一点:所谓“完全隔离”,不是靠技术堆砌实现的,而是靠放弃所有跨边界便利写法换来的。比如你不能再用 document.querySelector(".card-title") 去取值,也不能靠 class 名触发动画,所有通信必须走 attribute / property / event 三类标准通道。一旦开了口子,隔离就退化成心理安慰。











