必须为 class/id、localStorage 键、自定义事件和全局变量添加组件级前缀以避免全局冲突;CSS 与 JS 隔离边界需严格对齐,遵循 BEM 规范并限定作用域查询。

class 和 id 必须带组件级前缀
不加前缀的 class="title" 或 id="modal" 在多团队共存时必然冲突——HTML 没有原生模块作用域,所有 class/id 都在全局 flat 命名空间里。BEM 是最轻量且可落地的解法:class="user-profile" 是 block 名,class="user-profile__avatar" 是子元素,class="user-profile--loading" 是状态修饰符。禁止用 is-loading 这类通用前缀,它无法绑定到具体组件;也别写 big-red-text 这种样式描述名,主题切换后就失效。
localStorage 键必须拼接业务前缀
localStorage 是纯全局键值对,setItem("theme", "dark") 和第三方脚本写同名 key 会互相覆盖。不能靠“别人别乱写”来防御:
- 项目启动时定义常量:const STORAGE_PREFIX = "myapp_v3_user-profile_";
- 所有读写强制走前缀:localStorage.setItem(`${STORAGE_PREFIX}last-viewed`, "2026-05-01");
- 封装成工具函数,调用时只传逻辑键名,避免手动拼错
- 不推荐把多个字段塞进单个 key(如 "user-state"),否则更新一个字段就得重写整个对象,增加竞态风险
自定义事件和全局变量必须限定作用域
发 new CustomEvent("submit"),其他组件监听同名事件就会误收;挂 window.initWidget 也可能被覆盖:
- 事件类型加组件标识:"user-profile:submit"、"user-profile:load-error",冒号比连字符更易区分层级
- 全局变量全包进命名空间对象:window.UserProfile = { init(), destroy(), config: {} };,而不是零散挂 window.init 或 window.userConfig
- 第三方代码片段用 IIFE 包裹:(function() { /* 私有变量和函数 */ })();,这是安全兜底
CSS 和 JS 的隔离边界必须对齐
只改 class 名但 JS 还用 document.getElementById("modal"),或 CSS 写了 .user-profile__header 但 HTML 漏了根节点 class,隔离就形同虚设:
- JS 查询必须限定在组件根节点内:rootEl.querySelector(".user-profile__submit-btn")
- 根元素必须有唯一 block class,且所有子元素、状态类都严格遵循 BEM 规则
- 如果用了 Shadow DOM,JS 和 CSS 的作用域自然对齐;没用的话,人为约定就是唯一防线——而这条线一旦被跨过,冲突就从“可能”变成“必然”



















