Emmet: 爬升 `^` 与乘法 `*`:复杂树形结构与重复元素
上一课讲了"下钻"的
>和"平铺"的+。当结构复杂到 "兄弟需要回到上一层" 时,光有这俩就卡壳了——这时候就要用^往上爬一级;想一次写 5 个、10 个相同元素,就用*重复。
1. 你将学到
- 爬升操作符
^的语义:从当前嵌套层"跳出去"一级 - 多重爬升
^^^:连续向上跳多层,回到更外层上下文 - 乘法操作符
*N的语义:把前一个元素重复 N 次 *N与>的协作:>改变上下文,*N作用于当前层- 复杂树形结构的综合:括号的"留底层、爬升到指定层"模式
2. 一个前端开发者的真实故事
(1) 痛点:多层嵌套和"想回到上一层"卡壳
Alice 在重构一个电商首页,要把原本的"平铺"布局改成符合 HTML5 语义的三段式(header + main + footer)。她用上一课的 body>header+main+footer 写出了最外层,然后在 header 内部继续下钻 >nav>ul>li*5>a。但写完 header 部分后,问题来了:footer 跟在 main 后面应该是兄弟关系,但她发现 footer 跑到了 header 里面——因为 > 把上下文一层一层往下推,+ 默认就在最新那层追加。
更要命的是,她打算做一张 6 行 5 列的商品表格。6 行用 *6 没问题,但每一行里要重复 5 个单元格 td——手写 30 个 td,要么重复敲、要么写一行又长又难读的 tr>td*5*6。Alice 心想:如果能让 * 嵌套在 * 里就好了。
(2) ^ 与 * 的解法
Emmet 提供两个互补的操作符来解决这两个问题:
爬升 ^:当你写完 header 内部、想跳回 body 层级追加 footer 时,写 ^ 就能"爬出去"一级,回到父元素的兄弟位置。Alice 的完整结构只需要在 header 子树末尾加一个 ^,再接 main+footer:
body>header>nav>ul>li*5>a^^main+footer
^^ 让她从 ul 内部向上跳两层(先跳出 ul,再跳出 nav,回到 header),然后 +main+footer 跟 header 平级。
乘法 *:Emmet 的 * 可以放在任何元素后面,让它重复 N 次——而且 * 内部可以嵌套 > 形成子结构。表格用一行就能写完:
table>(tr>td*5)*6
tr>td*5 是一个"行模式",外面套 *6 让这个模式重复 6 次。从 30 个 td 到一行简写,从 5 分钟到 8 秒。
(3) 收益
^ 和 * 是 Emmet 处理"复杂结构"的核心组合。爬升让你能精确控制每个元素该放在哪一层;乘法让你能用一行简写批量生成重复结构。学完本课,Alice 用 body>header+main+footer 配合 ^^^ 完美控制层级,6×5 表格用 * 一行写完,从 12 分钟降到 25 秒。下一课的分组 () 会进一步让这些操作符"组合"得更强大。
3. 爬升操作符 ^:回到上一层
^ 表示爬升——把后续元素的位置从当前层回退到上一层。它对应一个常见场景:你在深层嵌套下写完了子树,想让下一个元素跟"祖先"平级,而不是继续陷在当前层。
| 写法 | 含义 |
|---|---|
^ |
向上爬 1 级(回退 1 层上下文) |
^^^ |
向上爬 3 级 |
^bq 等价于在上一级写 +bq |
爬升紧接着追加元素 |
(1) 单次爬升示意
| 缩写片段 | 形态 | 关键差异 |
|---|---|---|
div+div>p>span+em^bq |
bq 与 p 平级(在第二个 div 内) |
^ 触发回退 |
div+div>p>span+em+bq |
bq 与 span、em 平级(在 p 内) |
不带 ^,仍在 p 层 |
div>ul>li^p 中的 ^ 让 p 与 ul 平级(都在 div 内),而不是与 li 平级(li 内部)。
4. 乘法操作符 *:批量重复元素
*N 表示重复——把前一个元素重复 N 次。这是 Emmet 处理"列表项 N 个"、"表格行 N 行"、"卡片 N 张"这类重复结构的核心操作符。
| 写法 | 含义 |
|---|---|
li*5 |
5 个 li |
div.card*3 |
3 个 div.card(隐式 div,第 03 课) |
tr>td*6 |
1 个 tr,内含 6 个 td |
(tr>td*6)*5 |
5 个 tr,每个 tr 内含 6 个 td(第 06 课分组) |
*N 中的 N 必须是正整数。*1 是合法的(等价于不重复),但通常简化为单数。*0 在 Emmet 中会展开为空(不生成任何元素)。
(1) 乘法作用域
* 只作用于紧邻的前一个元素,遵循"局部上下文"规则。如果想让"一个嵌套子树"整体重复,必须用第 06 课的括号 () 分组:
| 缩写 | 重复范围 | 错误用法(如果想整体重复却漏括号) |
|---|---|---|
ul>li*3 |
3 个 li | — |
ul>li*3>a |
3 个 li,每个 li 含 1 个 a | — |
tr>td*5*6 |
❌ 只有 5 个 td,*6 重复 tr 整体(Emmet 解析为 td 共 5 个,tr 重复 6 次) |
想要"6 行 ×5 列"应该写 (tr>td*5)*6 |
5. 实战示例
下面 5 个示例覆盖 ^ 和 * 的全部核心场景——从单次爬升到多重爬升,从简单重复到嵌套分组重复。
▶ 示例:单次爬升 div+div>p>span+em^bq(难度⭐⭐)
上一课留下的问题:怎么让 bq 与 p 平级。答案是加一个 ^。
缩写:
div+div>p>span+em^bq
展开:
<div></div>
<div>
<p><span></span><em></em></p>
<blockquote></blockquote>
</div>
输出:
TEXT 📖 仅展示解析:div+div 创建两个 div 兄弟,>p 下钻到第二个 div 内放 p,>span+em 在 p 内放两个兄弟。最后 ^ 触发爬升:从 p 层回到 div 层(p 的父节点),然后 bq 与 p 平级,都作为第二个 div 的子节点。如果去掉 ^,bq 会跑到 p 内部,与 span 和 em 同级。
▶ 示例:多重爬升 div+div>p>span+em^^^bq(难度⭐⭐)
连续三个 ^ 直接爬到根级别。
缩写:
div+div>p>span+em^^^bq
展开:
<div></div>
<div>
<p><span></span><em></em></p>
</div>
<blockquote></blockquote>
输出:
TEXT 📖 仅展示`^^^` 连续爬升三次:第一次从 p 跳到第二个 div,第二次从第二个 div 跳到根,第三次从根再尝试但已经没有"上一层"(停在原地)。所以最终 bq 与第二个 div 平级,成为根的直接子节点。注意:根级别没有显式的容器(最外层 div 不存在),因此 bq 与 div 并列显示。这种"无根缩写"在浏览器中会自动被 `<body>` 包裹,但要注意层级变化对后续 CSS 选择器的影响。
▶ 示例:乘法重复 ul>li*5(难度⭐)
最基础的"批量生成 5 个 li"。
缩写:
ul>li*5
展开:
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
输出:
TEXT 📖 仅展示5 个空 li 默认作为 ul 的直接子节点。`*5` 直接作用于 li,不需要任何复杂结构。这是 Emmet 在生成"导航菜单 5 项"、"待办清单 5 条"、"特性卡片 5 张"等场景的最高频用法。配合内容填充(第 09 课)可以进一步让每一项带不同的文字。
▶ 示例:重复 + 嵌套 ul>li*3>a(难度⭐)
> 与 * 的经典组合——a 作为每个 li 的唯一子节点。
缩写:
ul>li*3>a
展开:
<ul>
<li><a href=""></a></li>
<li><a href=""></a></li>
<li><a href=""></a></li>
</ul>
输出:
TEXT 📖 仅展示`*3` 只重复 li,每个 li 内部都通过 > 包含一个 a。如果想让 a 也重复(即每个 li 内有 3 个 a),必须用括号:ul>li*>a*3 或把 a 套在分组里;如果想让"整个 (li 与 a)"作为一个单元重复,必须用 (li>a)*3(第 06 课分组)。这是 Emmet 中"作用域"的经典易错点。
▶ 示例:分组 + 重复 (div>dl>(dt+dd)*3)+footer>p(难度⭐⭐)
本课综合案例:嵌套的乘法 + 分组(() 是第 06 课内容,这里先预览用法)。
缩写:
(div>dl>(dt+dd)*3)+footer>p
展开:
<div>
<dl>
<dt></dt>
<dd></dd>
<dt></dt>
<dd></dd>
<dt></dt>
<dd></dd>
</dl>
</div>
<footer>
<p></p>
</footer>
输出:
TEXT 📖 仅展示解析:最外层 (div>dl>...) 作为一个分组,整体作为一个"虚拟根"。组内 div 包含 dl,dl 内的 (dt+dd)*3 把 dt+dd 这个兄弟对重复 3 次。然后 + 让 footer 与 div 平级(footer 是另一个独立子树),footer 内 >p 放一个 p。整行简写展开为 13 行 HTML:1 个 dl、6 个 dt/dd、1 个 footer、1 个 p。这就是 Emmet "一行写一个定义列表块"的典型用法。
6. 完整示例:电商商品表格 6×5
把 * 嵌套和分组用起来,6 行 5 列的商品表格用一行简写搞定。
▶ 示例:6×5 商品表格(难度⭐⭐)
缩写:
table>(tr>td*5)*6
展开:
<table>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
输出:
TEXT 📖 仅展示解析:tr>td*5 是"一行单元格"模板,外层 *6 重复 6 次这个模板。括号 (tr>td*5) 让"td*5"只作用于 tr 内部(5 个 td 都在同一个 tr 内),而不是直接被外层 *6 重复。两个 * 嵌套形成一个二维网格。如果不加括号写成 tr>td*5*6,Emmet 会解析为 tr 内 5 个 td,然后 tr 整体重复 6 次——这恰好也是 6×5 的结果,但语义不清晰,不推荐这么写。
❓ 常见问题
^ 操作符之前,怎么处理爬升需求?^ 是 2013 年加入的"逃生通道",专门解决这个痛点。^ 能一直爬到根吗?爬过头会怎样?^^^^^bq)会停在根,不会报错。Emmet 的设计是把根当作"最底层",多出来的 ^ 会被静默忽略。li*0 会展开成什么?*0 在官方 Emmet 中会生成 0 个元素(完全跳过),但在某些编辑器实现中会保留为单个空 li。建议不要写 *0,用单独写 li 代替。如果你写 *0 期待"生成 0 个"而编辑器给了你 1 个空元素,那是编辑器实现差异。div>span*3 和 div>span*3>p 展开后 p 在哪?<div><span><p></p></span><span><p></p></span><span><p></p></span></div>,p 是每个 span 的子节点(因为最近的上下文是 span)。*3 只重复 span,p 跟着每个 span。这是初学者最常混淆的场景:分清 * 的作用范围(仅前一个元素)和后续 > 的作用范围(最近上下文)。📖 小节
^表示爬升一级,把上下文从子节点回退到父节点^^^^^等多^连续爬升;爬过头停在虚拟根*N把紧邻的前一个元素重复 N 次,作用于当前最近的元素*N与>协作:>改变上下文,*N作用于新上下文里的最近元素- 嵌套乘法(如
*5的 tr 内含*6个 td)需要括号()分组(下节课讲) (tr>td*5)*6是"二维表格"的标准简写模式
📝 作业
- 基础题(难度⭐):用
*写出 3 个 Emmet 简写,分别展开为:(a) 5 个li;(b) 1 个ul,内含 3 个li,每个li含 1 个a;(c) 1 个ul,内含 1 个li,li内含 3 个a。 - 进阶题(难度⭐⭐):分析缩写
(nav>ul>li*3>a)+main+footer应该如何展开(先用纸笔画,再展开验证),然后改写为"用^实现"(不依赖括号)的等价版本。 - 挑战题(难度⭐⭐⭐):用一行 Emmet 简写写出一个 8×8 的棋盘
table(首格<th>表头,其余<td>单元格),并解释为什么这种结构的简写必须用括号分组;如果不用括号,写出"错误版本"并对比实际展开结果。