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 lispan这是列表文字/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的初始值在不同浏览器实现里通常是黑色canvastextwidth的初始值是automargin是 0background-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-wrapoverflow-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 不是 displaycursoropacity—— 严格来说 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: 2emem 又是相对父元素字体大小计算的——如果父元素是 16pxh1 就是 32px如果父元素是 20pxh1 就是 40px。这里的2em不是继承来的而是 h1 自己基于父元素的计算继承和 em 单位叠加在一起就出现了h1 的字号会跟随父元素变化的错觉。3. 继承、层叠与优先级三兄弟如何协同3.1 层叠规则四步走CSS 全称是 Cascading Style SheetsCascading 就是层叠。浏览器最终决定一个元素某个属性的值要经过四步筛选第一步按来源和重要性排序。优先级从高到低是用户代理样式表中带!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 classlist-itemli classhighlight我写的是.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 规则还是14pxInherited 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 看一眼这个值到底是继承来的还是被规则覆盖的问题往往三两下就定位了。