把 CSS 叫做“数据库”纯粹是胡扯——它连个 SELECT 语句都没有。但换个角度想,据库每个浏览器里都藏着一套隐形的据库“样式数据库”,只不过查询语言不是据库 SQL,而是据库选择器。你写 .header .nav li,据库浏览器就得在 DOM 树里翻箱倒柜,据库找出所有匹配的据库节点,再把样式规则挂上去。据库这个过程,据库本质上就是据库一次数据库查询,只不过索引结构换成了树遍历。据库
我头一回意识到这点,据库是据库调一个老项目的样式冲突。一个 !important 满天飞的据库页面,改一个按钮颜色,结果侧边栏的边框跟着变了。打开 DevTools 一看,好家伙,同一个元素身上叠了六层样式规则,来源分别是 reset.css、bootstrap 的遗留片段、三个内联 style,还有一个不知道谁写的 body * { transition: all 0.3s }。那时候我就想,要是 CSS 能像数据库那样 EXPLAIN 一下,告诉我哪条规则命中了、优先级怎么算的、有没有被覆盖,那得省多少头发。
浏览器确实做了类似的事,只是不叫“查询计划”。Chrome 的 style 面板里,划掉的属性就是被覆盖的,灰掉的选择器就是没匹配上的。但这些都是事后诸葛亮——你得先看到结果,才知道哪条规则赢了。真正写代码的时候,你面对的是一个黑箱:扔进去一堆选择器,它吐出来一个渲染树。中间发生了什么,全靠经验猜。
.list-item:hover { background: #f0f0f0 },但页面纹丝不动。最后发现是另一个组件库的样式表里有一句 .list-item:hover { background: transparent !important },而且那个样式表的加载顺序靠后。优先级相同,后加载的赢。这要是个数据库,加个索引或者改个查询顺序就完了,但在 CSS 里,你得翻遍所有引入的样式文件。 CSS 的“查询优化”基本靠人肉。选择器写得越具体,浏览器匹配越快,但可维护性越差。反过来,用 :where() 或者 :is() 能降低优先级,方便覆盖,可读性又上去了。这跟数据库设计里的索引权衡一模一样——你建了联合索引,查询快了,写入就慢了;你搞了太多索引,存储又炸了。CSS 没有自动优化器,每个选择器都是你亲手写的“执行计划”。
更麻烦的是,CSS 没有事务。你改一个 display,可能触发重排;改一个 color,可能只触发重绘。但浏览器不会告诉你这次修改影响了多少个节点,花了多少毫秒。Performance 面板能看,可那是事后分析。写的时候你只能凭感觉:transform 比 top 好,opacity 比 visibility 省。这些经验法则,本质上就是数据库里的“避免全表扫描”——别碰那些会触发 layout 的属性。
有人把 CSS-in-JS 当成救星,觉得把样式塞进 JavaScript 就能“类型安全”了。可运行时生成样式表,再插到 <style> 里,该冲突还是冲突。无非是把数据库的活儿挪到了应用层,查询还是那个查询,索引还是那个索引。甚至更糟——你多了一层运行时开销。
真正让我觉得 CSS 像数据库的,是 @layer 的出现。这玩意儿终于给了你一个显式的“命名空间”和“优先级层级”。以前你得靠 !important 或者超长选择器来压住别人,现在可以声明 @layer base, components, utilities;,然后按顺序写。这就像数据库里的 schema 隔离——不同模块的样式各归各的,谁也别越界。可惜浏览器支持是这两年才跟上,老项目里还是一锅粥。
所以别指望 CSS 能变成 SQL。它没有 ACID,没有 JOIN,没有 GROUP BY。但它有一套自己的查询语言——选择器,一套自己的索引——DOM 树,一套自己的优化器——浏览器渲染引擎。你写下的每一行样式,都是一次查询请求。区别在于,数据库会告诉你查询慢在哪,而 CSS 只会默默把页面画出来,然后让你对着 DevTools 里的划掉线发呆。
我现在写 CSS 的习惯是:尽量用低优先级的选择器,靠 @layer 管住顺序,少用 !important。这跟写 SQL 时避免 SELECT * 是一个道理——你越精确,别人越好接手。至于那些说“CSS 不是编程语言”的人,让他们去写写 :has() 嵌套加容器查询试试。那玩意儿比大部分 ORM 都复杂。