Emmet: 爬升 `^` 与乘法 `*`:复杂树形结构与重复元素

上一课讲了"下钻"的 > 和"平铺"的 +。当结构复杂到 "兄弟需要回到上一层" 时,光有这俩就卡壳了——这时候就要用 ^ 往上爬一级;想一次写 5 个、10 个相同元素,就用 * 重复。

1. 你将学到


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

TEXT 📖 仅展示
body>header>nav>ul>li*5>a^^main+footer

^^ 让她从 ul 内部向上跳两层(先跳出 ul,再跳出 nav,回到 header),然后 +main+footer 跟 header 平级。

乘法 *:Emmet 的 * 可以放在任何元素后面,让它重复 N 次——而且 * 内部可以嵌套 > 形成子结构。表格用一行就能写完:

TEXT 📖 仅展示
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 bqp 平级(在第二个 div 内) ^ 触发回退
div+div>p>span+em+bq bqspan、em 平级(在 p 内) 不带 ^,仍在 p 层
📌 重点: 爬升是 Emmet 最容易让初学者困惑的操作符之一。它的"上一次上下文"指的是语法树上的父节点,不是"上一次我写过的元素"。也就是说,div>ul>li^p 中的 ^pul 平级(都在 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(难度⭐⭐)

上一课留下的问题:怎么让 bqp 平级。答案是加一个 ^

缩写:

HTML
div+div>p>span+em^bq

展开:

HTML
<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(难度⭐⭐)

连续三个 ^ 直接爬到根级别。

缩写:

HTML
div+div>p>span+em^^^bq

展开:

HTML
<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"。

缩写:

HTML
ul>li*5

展开:

HTML
<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 的唯一子节点。

缩写:

HTML
ul>li*3>a

展开:

HTML
<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 中"作用域"的经典易错点。

本课综合案例:嵌套的乘法 + 分组(() 是第 06 课内容,这里先预览用法)。

缩写:

HTML
(div>dl>(dt+dd)*3)+footer>p

展开:

HTML
<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 商品表格(难度⭐⭐)

缩写:

HTML
table>(tr>td*5)*6

展开:

HTML
<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 的结果,但语义不清晰,不推荐这么写。

❓ 常见问题

Q 没有 ^ 操作符之前,怎么处理爬升需求?
A 在 Emmet 早期版本里确实没有爬升符,那时只能用"分组 + 重新下钻"模拟爬升。例如 div>header>p+span^(main+footer) 不存在,只能写 (header>p+span)+(main+footer) 用分组绕开。但分组写法可读性差,且需要重新下钻。^ 是 2013 年加入的"逃生通道",专门解决这个痛点。
Q ^ 能一直爬到根吗?爬过头会怎样?
A 能一直爬,但最多爬到根(虚拟根)。爬过头(例如在根位置写 ^^^^^bq)会停在根,不会报错。Emmet 的设计是把根当作"最底层",多出来的 ^ 会被静默忽略。
Q li*0 会展开成什么?
A *0 在官方 Emmet 中会生成 0 个元素(完全跳过),但在某些编辑器实现中会保留为单个空 li。建议不要写 *0,用单独写 li 代替。如果你写 *0 期待"生成 0 个"而编辑器给了你 1 个空元素,那是编辑器实现差异。
Q div>span*3div>span*3>p 展开后 p 在哪?
A 展开为 <div><span><p></p></span><span><p></p></span><span><p></p></span></div>,p 是每个 span 的子节点(因为最近的上下文是 span)。*3 只重复 span,p 跟着每个 span。这是初学者最常混淆的场景:分清 * 的作用范围(仅前一个元素)和后续 > 的作用范围(最近上下文)。

📖 小节


📝 作业

  1. 基础题(难度⭐):用 * 写出 3 个 Emmet 简写,分别展开为:(a) 5 个 li;(b) 1 个 ul,内含 3 个 li,每个 li 含 1 个 a;(c) 1 个 ul,内含 1 个 lili 内含 3 个 a
  2. 进阶题(难度⭐⭐):分析缩写 (nav>ul>li*3>a)+main+footer 应该如何展开(先用纸笔画,再展开验证),然后改写为"用 ^ 实现"(不依赖括号)的等价版本。
  3. 挑战题(难度⭐⭐⭐):用一行 Emmet 简写写出一个 8×8 的棋盘 table(首格 <th> 表头,其余 <td> 单元格),并解释为什么这种结构的简写必须用括号分组;如果不用括号,写出"错误版本"并对比实际展开结果。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏