Vue.js 的 Scoped CSS 隔离由编译阶段注入 data-v-hash 属性与 CSS 选择器重写共同实现,VNode 仅携带该属性而不参与作用域逻辑,实际隔离依赖浏览器 CSS 引擎匹配。

Vue.js 中的 Scoped CSS 作用域隔离,并不是由 VNode 直接“存储” scopedId 来实现的,而是编译阶段注入属性 + 运行时渲染配合 CSS 选择器匹配共同完成的。VNode 本身不保存 scopedId,但它会携带由编译器注入的唯一属性(如 data-v-f3f264d4),这个属性名中的哈希值(如 f3f264d4)才是所谓“scopedId”的实质体现。
编译阶段:为元素注入唯一属性
当使用 <style scoped></style> 时,Vue 的模板编译器(vue/compiler-sfc)会对单文件组件中的每个非<style></style>标签节点,在生成 AST 后、生成 render 函数前,自动添加一个特殊属性:
- 该属性形如
data-v-f3f264d4,其中哈希值由组件文件路径或内容计算得出,保证组件级唯一 - 所有普通元素(如
<div>、<code><p></p>)都会被加上该属性;但子组件根节点、v-html内容、<slot></slot>插槽内容等默认不加(可配置) - 该属性最终会作为 vnode 的
props或attrs被写入 VNode 对象(例如vnode.props = { 'data-v-f3f264d4': '' }) - 将
.btn { color: red; }编译为.btn[data-v-f3f264d4] { color: red; } - 深层选择器如
.parent ::v-deep .child或.parent /deep/ .child会被转为.parent[data-v-f3f264d4] .child(保留后代关系,不强制加属性) - 此过程与 VNode 无关,纯构建时行为,输出的 CSS 已带 scope 属性约束
- render 函数执行后生成 VNode 树,挂载时创建真实 DOM 元素,并把
data-v-xxx属性写入对应 DOM 节点 - 浏览器解析 CSS 时,只对同时满足“类名正确”且“存在对应 data-v-xxx 属性”的元素应用样式
- 由于子组件 DOM 默认不带父组件的
data-v-xxx,其样式自然不会被父组件 scoped CSS 影响 —— 隔离由此达成 - VNode 是纯粹的数据结构,它只是如实携带编译器塞进来的属性,不理解其语义
- 没有 runtime 的“作用域检查逻辑”,不存在 VNode 对比时根据 scopedId 做样式过滤
- 若手动在模板中写死
data-v-xxx,或通过innerHTML注入带该属性的 HTML,同样会命中 scoped 样式 —— 证明机制完全依赖 DOM 属性 + CSS 选择器匹配
CSS 编译阶段:重写选择器,绑定同一 scope ID
对应的 <style scoped></style> 会被 vue/compiler-sfc 中的样式预处理器(如 postcss-plugin-scoped)处理:
运行时:VNode 渲染 + 浏览器原生 CSS 匹配
真正起隔离作用的是浏览器 CSS 引擎,VNode 只是“搬运工”:
立即学习“前端免费学习笔记(深入)”;
注意边界:VNode 不“管理” scopedId,也不做运行时校验
Scoped ID 的生成、注入、CSS 重写全部发生在构建阶段(SFC 编译期),运行时 Vue 不解析、不校验、不动态修改 scopedId:
所以准确地说:scopedId 是编译产出的标识符,VNode 是它的载体之一,而样式隔离的本质是 CSS 属性选择器的天然作用域约束能力。


















