1. 先说结论:CSS 继承到底是什么
1.1 一个最直观的例子
写 H5 页面的同学,十有八九遇到过这种场景:给body设了color: #333,结果整个页面所有没单独设置颜色的文字,统统变成了 #333;给body设了font-family: "PingFang SC", sans-serif,所有子元素的字体都跟着变了。这就是 CSS 继承在起作用。
我平时带新人,最喜欢用这个代码演示:
<style> body { color: #333; font-size: 16px; font-family: "PingFang SC", "Microsoft YaHei", sans-serif; } </style> <body> <div> <p>这是段落文字</p> <ul> <li><span>这是列表文字</span></li> </ul> </div> </body>打开浏览器,你根本不用给p、ul、li、span写任何样式,它们的文字颜色、字体、字号,全跟着body走了。这就是继承——向下的、自动的、默认的样式传递机制。
理解继承最核心的一句话:当某个元素没有被直接指定某个属性值时,浏览器会沿着 DOM 树向上找父元素,把父元素计算后的属性值拿过来用。它解决的痛点是:不用在每个标签上重复写color、font-family这些基础属性,省掉大量冗余代码,也让整站的视觉基调可以在一处统一定义。
1.2 继承的本质是"属性下行",不是"样式复制"
有初学 H5 的朋友会误解:继承是不是像复制粘贴一样,把父元素整段 style 拷贝给子元素?不是的。这里必须分清两个概念。
- 继承针对的是"属性值",不是"样式规则"。父元素写了
color: red,子元素继承的是"颜色值 red"这个计算结果;如果父元素的颜色是currentColor或通过变量算出来的中间值,子元素拿到的也是最终计算后的值。 - 并非所有属性都参与继承。你给父元素设了
border: 1px solid #ccc,子元素不会自动带上边框。给父元素设了background,子元素也不会自动铺上背景色。这个"能继承哪些属性"的边界,是本期笔记最值钱的部分。 - 继承只沿 DOM 树向下传递,不向上、不横向。兄弟元素之间没有任何继承关系,子元素影响不了父元素。
我见过有人为了"让子元素继承背景色",给父元素写了 background,发现子元素没有背景,又去给每个子元素补一遍 —— 这就是没搞懂继承的边界。实际上背景不会继承,但有个视觉错觉:背景色默认是transparent,父元素的背景通常会透过子元素显示出来,看起来像"继承"了。这是透明的特性,不是继承。
1.3 理解继承前必须搞懂的三个前置概念
要把继承讲透,先得把它的"队友"认识一下,否则后面看规则会一头雾水。
第一个是属性初始值(initial value)。CSS 里每个属性都有一个出厂默认值,比如color的初始值在不同浏览器实现里通常是黑色(canvastext),width的初始值是auto,margin是 0,background-color是transparent。当一个元素没有继承值、也没有直接声明值时,浏览器就用初始值。继承值和初始值是两种完全不同的"兜底方案",很多人混淆。
第二个是 DOM 树和渲染树的关系。继承发生在 DOM 树上的父与子之间。display: none的元素不会渲染,它的子元素也不会渲染,但继承计算在样式系统里仍然存在——只是看不见而已。这一点在做 SPA 切换、动态显示隐藏的时候容易踩坑。
第三个是"相对单位"会参与继承的计算。font-size继承的不只是数值,还有单位语义。父元素font-size: 20px,子元素不设字号则继承 20px;但如果子元素设了font-size: 1.5em,这个 1.5em 是相对于继承来的字号 20px计算的,结果就是 30px。这就是为什么嵌套em会越嵌越大,很多新手在这个地方栽跟头。
这三个概念就像房子的地基。地基打好了,再看"哪些属性继承、哪些不继承",你就能理解每个表项背后的"为什么",而不用死记硬背。
2. 哪些属性会继承,哪些不会——这张表请收好
2.1 会继承的属性清单(记忆口诀)
MDN 官方的继承属性能列出一长串,但实际做 H5 开发,真正高频需要记住的没那么多。我按使用频率给你分好类。
文本相关(最重要):
color(文字颜色)font-family、font-size、font-weight、font-style、font-variant、font-stretch、line-height、letter-spacing、word-spacing、text-align、text-indent、text-transform、white-space、word-break、word-wrap(overflow-wrap)、text-shadow、direction、tab-size
列表相关:
list-style、list-style-type、list-style-position、list-style-image
表格相关:
border-collapse、border-spacing、caption-side、empty-cells、table-layout
其他:
visibility(注意:是 visibility 不是 display)cursoropacity?—— 严格来说 opacity 不是继承属性,但子元素会被父元素的 opacity 影响,因为它创建了新的层叠上下文。这点很多人搞混,后面细说。quotes、speak等偏冷门。
记的时候用口诀:"字色颜体行,列表游标可见性"——文字颜色、字体系列、行高、列表样式、cursor、visibility。我面试初级前端常问这个,能脱口而出这几个的,通常对 CSS 模型是有理解的。
为什么这些属性要设计成继承?因为它们是"内容表现"属性。文字是树状嵌套的,段落、行内文本、列表项天然是一层套一层,如果不继承,每层都要显式设置字体和颜色,写出来的 CSS 能累死人。浏览器厂商在设计 CSS2 规范时,遵循的原则就是"内容样式可继承,盒模型样式不继承"。
2.2 不会继承的属性清单(本质原因)
盒模型相关全都不继承:
width、height、min-width、max-width、min-height、max-height、margin、padding、border(含 border-width/style/color)、box-sizing、box-shadow、outline
布局与定位相关全都不继承:
display、position、top/right/bottom/left、float、clear、flex相关(flex-direction、justify-content、align-items 等)、grid相关
背景相关全都不继承:
background及其所有子属性(background-color、background-image、background-size 等)
其他高频:
transform、transition、animationvertical-align、z-index、overflowtext-decoration(这个特别容易踩坑,下划线不继承,但默认会"渗"到子元素视觉上)
为什么这些不继承?想想就明白了:如果width能继承,父元素设 300px,所有子元素全变成 300px,布局直接崩。盒模型和布局属性是"每个元素独立的",没有理由向下传递。如果你真的希望子元素和父元素宽度一致,应该用width: inherit主动声明,而不是指望默认继承。
2.3 继承与初始值:边界场景怎么处理
有个常见疑问:a标签的颜色为什么设了body { color: red }不变蓝?不是说 color 可继承吗?
这里涉及的其实是**浏览器默认样式(user agent stylesheet)**的优先级问题。浏览器自带的样式表给a设置了color: -webkit-link(蓝色)和text-decoration: underline。当作者样式(你写的 CSS)没有直接作用于 a 标签时,a 标签用的是浏览器默认样式,而不是继承值。因为浏览器默认样式属于"用户代理样式",它的优先级高于继承值。
所以如果你想让页面里所有 a 标签跟随继承的颜色,必须显式写:
a { color: inherit; text-decoration: none; }这是 reset.css 里最常见的两行。理解了"继承值优先级低于浏览器默认样式"这个规则,就明白为什么要这样处理了。
再举一个初始值的例子:h1的font-size是 2em,不是继承的。浏览器默认样式给 h1 设置了font-size: 2em,em 又是相对父元素字体大小计算的——如果父元素是 16px,h1 就是 32px;如果父元素是 20px,h1 就是 40px。这里的"2em"不是继承来的,而是 h1 自己基于父元素的计算,继承和 em 单位叠加在一起,就出现了"h1 的字号会跟随父元素变化"的错觉。
3. 继承、层叠与优先级:三兄弟如何协同
3.1 层叠规则四步走
CSS 全称是 Cascading Style Sheets,Cascading 就是"层叠"。浏览器最终决定一个元素某个属性的值,要经过四步筛选:
第一步:按来源和重要性排序。优先级从高到低是:用户代理样式表中带!important的 > 用户样式表中带!important的 > 作者样式表中带!important的 > 作者样式表常规声明 > 用户样式表常规声明 > 用户代理样式表常规声明。实际上大多数项目只关心"作者样式表"这一层。
第二步:按选择器优先级排序。只有来源层次相同才比这一层。优先级计算规则是(行内样式, ID选择器个数, 类/属性/伪类个数, 类型/伪元素个数)。行内 style 优先级最高,然后依次是 ID、类、标签。这一点我做 H5 页面时天天用,后面有实战演练。
第三步:按书写顺序排序。优先级相同,后写的覆盖先写的。
第四步:继承值兜底。如果以上三层都没有命中某个属性,浏览器才考虑继承值;如果该属性不可继承,就用初始值。
重点来了:继承值在这套流程里是最后一位。也就是说,任何一条哪怕优先级最低的作者样式(比如* { color: blue }),都能覆盖掉父元素传给子元素的继承值。这是理解"为什么子元素颜色改不动"的关键。
3.2 优先级计算的实战演练
我拿一个真实改版中遇到的例子来说。某次在 H5 页面里要给一个列表项加红色强调,写了:
.list-item .highlight { color: red; }但页面上还有一条全局样式:
ul li { color: #333; }我心里预期红色生效,结果实际渲染是 #333。排查了一会儿才反应过来:.list-item .highlight是"两个类选择器",优先级是 0,0,2,0;而ul li是"两个类型选择器",优先级是 0,0,0,2。前者更高,理论上 red 应该赢。那问题出在哪?
后来发现真实 DOM 是<ul class="list-item"><li class="highlight">,我写的是.list-item .highlight,确实匹配了 li。但覆盖它的其实是另一条样式:.item-list li.highlight——这个选择器是"1个类+1个类型+1个类",算下来是 0,0,2,1,比我的 0,0,2,0 高出了一点点,所以它赢了。
这种"差一点点"的优先级争斗在大型项目里特别常见。我的经验是:不要试图用堆选择器个数去 PK,而是直接给目标元素加一个独立的类名,或者用更高的优先级手段(比如增加一层 HTML 父级)。写选择器的时候,经常需要心里默算一下优先级四元组:
/* 优先级 (0,1,0,0) 记作 0-1-0-0 */ #app .content p { /* 0-1-1-1 */ color: red; } /* 优先级 0-0-2-0 的规则赢不过上面的 0-1-1-1 */ .app .wrapper p { /* 0-0-2-1 */ color: blue; }还要记住一个常被忽略的规则:行内样式优先级高于任何选择器,除非作者样式里出现!important。所以不要在 HTML 上写 style 去覆盖类名选择器,后期维护会非常痛苦。
3.3 为什么子元素样式"覆盖不掉":继承值在层叠中是垫底的
这个问题的经典场景是:父元素设了font-size: 12px,子元素里写了一堆.child { font-size: 14px },但有个别子元素死活还是 12px。
查半天发现,那个"死活改不动"的子元素,其实是被更具体的选择器命中了,或者那条 14px 的规则被别的同等优先级后写的规则覆盖了。但还有一种隐蔽情况:你给子元素设的是font-size: 1.2em,它继承父元素的 12px,算出来是 14.4px,而你想让它覆盖 12px,结果视觉上是 14.4px——四舍五入到 14px 可能看不出差别,但实际值不是 14px。
还有一个常见误解:有人以为子元素设了font-size: 14px就能完全摆脱父元素影响。实际上,如果这条规则没被应用(比如选择器写错了、优先级不够、被更低的层叠来源忽略了),子元素就会落到"继承值"这一层,直接用父元素的 12px。浏览器 DevTools 里能看到 computed 面板显示 "Inherited from body",一眼就能看出来。
记住一句话:继承值不是"规则",它是"没规则时的默认回退"。排查样式问题时,先看 Elements 面板右侧 Computed,找到这个属性到底来自哪里——是继承、是某条规则、还是 initial,问题原因基本就浮出水面了。
4. 实操:控制继承的四种武器
4.1 inherit:主动"认祖归宗"
CSS 提供了inherit关键字,可以让某个属性强制使用父元素的计算值,哪怕这个属性默认不继承。
实际项目里最常见的用法就是按钮和表单控件。浏览器给button、input、select设置了默认字体,往往和页面字体不一致。我每次写 H5 页面都要加这么一段:
button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }这样表单控件就会跟页面其他文字体系统一起来。如果不加,你在 body 里设的font-family对 button 是无效的,因为 button 用的是系统默认字体。这个坑我在第 3 期笔记里也提过。
另一个高频用法是text-align: inherit或者是width: inherit,用来让子元素强制对齐父元素的文本居中或宽度。
4.2 initial:一键回到初始值
initial关键字让属性回到 CSS 规范定义的初始值,不管父元素是什么、之前设了什么。比如:
div { color: initial; /* 回到黑色(canvastext) */ font-size: initial; /* 回到 medium,通常 16px */ display: initial; /* 回到 inline */ }有个非常实用的场景:你引入了一个第三方组件,组件内部给某个元素设了color: #999之类的样式,想抹掉它,但不想用优先级去 PK,也不想去改第三方源码,直接给该元素写color: initial就能把它拉回"出厂值"。
要注意的是,initial不等于inherit,更不等于"无样式"——它回的是初始值,不是父元素的继承值。
4.3 unset 与 revert:偷懒与保底
unset是个偷懒神器:如果这个属性默认能继承,unset相当于inherit;如果默认不继承,unset相当于initial。它的语义就是"回到我应该有的状态"。
举个例子,你想让某个元素的所有"可继承属性"都跟随父元素,其他属性回到初始值,一行all: unset就能做到:
.reset-me { all: unset; /* 所有属性回到默认行为 */ }revert则稍微不一样:它回退到**用户代理样式表(浏览器默认样式)**设定的值。也就是说,revert会撤销你写的作者样式,回到浏览器默认表现。比如你重置了h1 { font-size: 16px },再想让它回到浏览器默认的 2em,写font-size: revert就行了。
这四个关键字的记忆方式:
| 关键字 | 行为 | 适用场景 |
|---|---|---|
inherit | 强制使用父元素计算值 | 表单控件字体统一 |
initial | 回属性初始值 | 抹掉第三方组件样式 |
unset | 能继承则继承,否则初始值 | 一键重置所有属性 |
revert | 回浏览器默认样式 | 撤销作者样式 |
4.4 CSS 自定义属性的继承特性
CSS 变量(自定义属性)是继承的,这一点经常被忽略。定义在:root上的变量,全局所有元素都能用。定义在某个父元素上的变量,子元素也能用:
:root { --theme-color: #1890ff; } .box { --box-color: #fa8c16; } .box .inner { color: var(--box-color); /* 能用父级定义的变量 */ background: var(--theme-color); /* 也能用全局变量 */ }这个特性非常实用。做 H5 主题切换、品牌色管理,都是利用变量继承做到"一处修改,全局生效"。但要注意:变量继承沿 DOM 树生效,如果你在子元素里重定义同名变量,会遮蔽父元素的值。这跟 JS 里变量作用域链的遮蔽非常像,搞混了就会出现"个别组件颜色怪怪的"问题。
另外一个坑:CSS 变量不支持在@media里直接重新定义后继承吗?其实是支持的,媒体查询里改:root的变量,所有子元素都会跟着变。但千万别忘了变量取值是在使用处解析的,继承的是"变量值引用链"。
5. 常见问题与排查技巧实录
5.1 为什么 font-size 设置了子元素却不生效
这是一个高频问题。现象:父元素font-size: 14px,子元素写了.son { font-size: 16px },但浏览器里计算值还是 14px。
排查步骤:
- 打开 DevTools,选中子元素,看 Computed 面板里 font-size 的显示,它会标注是"16px(来自 .son 规则)"还是"14px(Inherited from .parent)"。
- 如果是 Inherited,说明你的 .son 规则没匹配上或优先级不够。常见原因是选择器写成了
.parent .son,但实际 DOM 层级里 .son 不是 .parent 的后代;或者那条规则被同优先级但更靠后的规则覆盖了。 - 如果规则明明列在那里但显示"无效属性值",检查有没有拼写错误,或者是不是用了
font: 16px这种简写覆盖了之前设定的font-size。
我这边处理过最离谱的一次:同事在样式表里写了font-size: 16,漏了单位。浏览器解析失败,直接把这条规则丢弃,子元素就落回继承值了。这类问题 DevTools 的 Issues 面板会提示"Invalid property value",但很多人不看。
5.2 DevTools 中查看继承值的正确姿势
Chrome DevTools 的 Elements > Computed 面板是排查继承问题的最强工具。我会按这个顺序操作:
- 选中目标元素,查看 Computed 列表,按属性名搜索。
- 点击属性值左边的小箭头,会展开"此值的来源",标注是
Inherited from div.parent还是某条具体 CSS 规则(还会显示所在文件和行号)。 - 切换"Show all"按钮,能看所有属性,包括未设置的属性和它们的计算值来源。
还有一个实用技巧:在 Computed 面板里直接修改继承来的值(比如把继承的 color 改成别的),可以临时验证"如果这里有个显式规则会长什么样"。改完刷新就恢复,调试非常高效。
如果项目用的是 Chrome 的 CSS Overview,还能看到整页用到的颜色、字体分布,这对于做继承审计也有帮助——一眼看出哪些元素在无意中继承了哪个字体。
5.3 组件化时代:reset 与继承的相爱相杀
现在做 H5 基本都是组件化开发(Vue、React、小程序),组件之间样式隔离越来越重要。继承在这里是把双刃剑:
- 好处:组件根节点设一个字体,内部所有文本统一,不用逐层设置。
- 坏处:父组件的样式会"渗"进子组件,导致子组件表现不可控。
解决方案我通常按这三个层次做:
第一层:全局 reset。用 normalize.css 或自写的 reset,先把浏览器默认样式拉平。比如a { color: inherit }、ul, ol { list-style: none }、button { font-family: inherit }。这些本质上都是在管理"继承边界"。
第二层:组件根节点显式重置。给每个组件的根元素加一条规则,把不希望继承的属性显式指回去。比如:
.component-root { font-family: inherit; color: inherit; all: initial; /* 谨慎使用,但有时很有效 */ }第三层:用 CSS Modules / scoped 样式隔离。现代框架的 scoped 样式会给选择器加属性或类名哈希,但这只能隔离选择器,隔离不了继承。所以 scoped 项目里依然要遵守"根节点显式声明关键属性"的约定。
我自己的习惯是:每次新建一个 H5 页面组件,先写三行:font-family: inherit; color: inherit; font-size: inherit;。看起来啰嗦,但能避免好多"莫名被全局样式带偏"的问题。
5.4 继承引发的"样式泄漏":如何隔离
最后说一个真实项目里最磨人的问题:全局字体、颜色、行高,因为继承关系,渗透到了不该渗透的地方。
典型场景:页面某块区域用第三方富文本编辑器展示内容,编辑器内部有自己的一套样式体系。但编辑器所在容器继承了你的line-height: 1.8,导致编辑器内部的段落间距全变了。
处理办法有两个方向:
方向一:在容器上掐断继承。
.editor-wrapper { line-height: initial; /* 掐断行高继承 */ font-size: initial; color: initial; }注意,initial只能掐掉当前元素的继承值,如果编辑器内部自己的样式依赖继承(比如它的子元素没设 line-height),那就还是会继承 editor-wrapper 的 initial 值。所以更彻底的是all: initial,但这对编辑器内部样式影响太大,谨慎使用。
方向二:用all: revert让该区域回到浏览器默认样式。这样第三方组件就能基于浏览器默认值来运作,不受你的全局影响。
从架构角度讲,我建议把所有全局性的字体、颜色、行高都定义在 CSS 变量里,而不是直接写死在 body 上。这样每个组件都能通过var(--font-size-base)明确取值,而不是靠"运气继承"。
回到开头那个例子:给body设了 color 和 font-family,页面文字都统一了,这是继承最棒的一面。但也正因为继承是自动的、默许的、无感的,一旦页面复杂起来,"看不见的改变"就会变成"查不出的 bug"。所以我的原则很简单——享受继承的便利,同时时刻知道它的边界在哪。下次再遇到样式对不上的问题,先别急着加选择器,打开 Computed 看一眼这个值到底是继承来的,还是被规则覆盖的,问题往往三两下就定位了。