文本对象:给「东西」起名字
学习目标
- 理解文本对象在编辑语言里的身份:一类新的名词,和移动键并列
- 掌握两条边界:i(inside,里面)与 a(around,周围)
- 记住四族常用对象:词 iw/aw、括号 i(/a(、引号 i"/a"、标签 it/at
- 会用 ciw(改这个词)、ci"(改字符串内容)、ci((改参数列表)这三个高频句子
- 建立关键直觉:光标只需在对象内部任意位置,不用先跳到边界上
- 理解嵌套规则:块对象永远作用于光标所在的最内层
1. 从一个别扭的瞬间说起
第 2 章结尾你学会了 dt,(删到逗号前)。用几天你会碰到三个新烦恼:
- 想把
render(header, footer)里的参数整个清空重写 —— dt) 删不干净,df) 又把右括号也误伤; - 想改
"Ada"引号里的内容 —— 先 f" 跳进去、再 c$ 删到引号前?三步还容易失手; - 删一个词用 dw 挺好,可光标停在词中间时,它只删半个词,剩下的半截更难看。
这些烦恼的共同点:你在用「移动」描述范围 —— 从这里、到那里。但大脑里真正想说的是:「作用于这个东西」:这个词、这对括号里的东西、这对引号里的东西。鼠标时代的你早就习惯了这个思路:双击选中一个词、点在括号里让 IDE 高亮配对。Vim 给这个思路一个正式的名字 —— 文本对象(text object),并且把它做成了一类新的名词,任何动词都能用。
先看一眼它解决开头三个烦恼的样子:di( 删掉括号里全部内容(括号完好)、ci" 清空字符串内容原地重打、daw 删掉光标所在的整个词(光标在词里随便哪个字母上都行)。三个键,说一句话。本章就把这套「给东西起名字」的词典讲全。
值得先想清楚一次思维层面的差别。「移动思维」是路径式的:从这里出发、经过哪些词、到哪里停 —— 你在描述一段旅程,光标是旅行者,所以你必须关心它的起点和终点。「对象思维」是指物式的:那个词、那对括号、那段引号 —— 你在指认一个东西,至于光标具体站在这个东西的哪个字母上,无关紧要。这就是为什么对象能把「光标位置」从方程里消掉:它不问你在哪,只问你在谁的肚子里。练习本章时的自检标准也只有一条:每次按对象键之前,你脑子里出现的是「路线」还是「东西」 —— 前者说明还在用第 2 章的脑子,后者才算是换挡成功。
2. 语法:动词 + i/a + 对象
文本对象的构词法只有两段:
[动词] i 或 a + 对象字符
- i = inside(里面):只含对象本身,不含边界;
inner 的缩写; - a = around(周围):含边界,并且连带一侧的空白(词对象连尾部空格,块对象连包裹符号本身)。
对象字符目前你能用到的全家族如下,一张表收走:
| 对象 | 指什么 | 对象 | 指什么 |
|---|---|---|---|
| w / W | 词 / 大词 | ( 或 b | ( ) 括号块 |
| s | 句子(英文句号分隔) | [ | [ ] 方括号块 |
| p | 段落(空行分隔) | { 或 B | { } 花括号块 |
| " ' ` | 引号串(三种引号各自) | < > | < > 尖括号块 |
| t | HTML/XML 标签 | ) ] } | 同各自开括号(闭括号也能用) |
注意一个重要身份区别:第 2 章的移动键是「到达某处」的名词(回答「到哪」),文本对象是「框住某段」的名词(回答「哪一段」)。前者把光标送过去,后者原地圈地 —— 所以文本对象不能单独用来移动,它天生是给动词当宾语的。
3. 词对象:iw 与 aw
第一站是最常用的 w。iw = 词本身;aw = 词 + 尾部空格。两者的差异在删除后立刻可见:
看清楚那个细节了么?diw 留下了两个连续空格(词没了,它两侧的空格都还在),而 daw 把尾部空格一起带走,只剩一个空格,排版干净。记住一个不成文的口味偏好:在代码行里删词,优先 aw(不留双空格);要动"词本身"而保留间隔时用 iw(比如 ciw 改名时,两侧空格根本不该动)。
再往上配一个动词 c(change,改 = 删 + 进插入),就是你以后每天要按几十次的组合:
| 句子 | 读法 | 什么时候用 |
|---|---|---|
| ciw | 改·内·词 | 变量改名 —— 光标在词内任意位置,不用 b 回词头 |
| daw | 删·整·词 | 删掉一个词且不留双空格 |
| yiw | 复制·内·词 | 抓一个词进寄存器(下一章细讲 y) |
| caw | 改·整·词 | 少用:会把尾部空格也吞进插入点 |
4. 括号家族:i( 与 a(
代码里最多的结构就是配对符:( )、{ }、[ ]。对象语法对它们一视同仁 —— 按 (、[、{(或对应的闭括号,效果相同;b/B 是 (/{ 的别名,b = block)。先看最重要的性质:
演示里光标自始至终停在 header 的字母中间 —— 不在括号上,更不在行首。di( 照样精准清空括号内容。这条性质值得单独加粗:块对象要求光标在块内(边界上也行),但不要求在任何特定位置。对比第 2 章的 f:你得先「看到」锚点字符再跳过去 —— 对象把这一步完全省了。
三种高频用法,配不同的动词:
| 句子 | 含义 | 典型场景 |
|---|---|---|
| di( | 清空括号内容,括号保留 | 参数不要了,重写;清空调用参数做占位 |
| ci( | 清空并直接重打 | 改参数列表(下一章实战的主力) |
| da( | 连括号一起删 | 去掉一层「包裹」:把 f(x) 变 f |
| yi( | 复制括号内全部内容 | 把参数抄到别处(第 9 章寄存器配合) |
花括号同理:di{ 清空函数体、ci{ 重写函数体、yi{ 复制整个块。嵌套时只有一条规则:
永远取光标所在的最内层。光标在 y() 上,那是 if 块的地盘;光标挪到 if 这个词上(已经在 if 块外、函数块内),同一个 di{ 就改判为函数体。想让外层响应?把光标挪出内层再按就是 —— 用 jk 移动几格就能换目标,这个「换挡」成本低到可以忽略。
5. 引号家族:i" 与 a"
三种引号各成一个对象:"、'、`(模板字符串)。改字符串内容是写业务代码的日常 —— 日志文案、错误消息、UI 文案,全是它:
ci" 的体验值得回味:不用 f 找引号、不用数长度,光标放在引号串里任何一个字符上,三个键,内容清空、引弧保留、光标停在原地等你打字。配合 Esc,一次文案修改 4 个动作封顶。而 da" 连引号端走,适合「这个字符串整个不要了」。
两条使用规则:①光标必须在这对引号之间(引号上也算);②引号必须同种配对 —— "…' 不算一对。另外注意转义的坑:字符串里的 \" 会被 Vim 正确跳过,但 IDE 的语法解析和 Vim 的朴素匹配偶有出入,极复杂的情况(引号里嵌引号)还是用第 8 章的替换更稳。
6. 段落对象:ip 与 ap
「段落」在 Vim 里的定义朴素得可爱:空行分隔出来的一坨连续文本。代码文件里,函数与函数之间、配置块与配置块之间恰好都用空行隔开 —— 于是 ip(inner paragraph)天然对应「这一坨代码」:
看到那个容易踩的坑了吗 —— 这里的 p 是 paragraph(段落对象),不是下一章的粘贴键 p(paste)。同一个字母,位置决定身份:跟在 i/a 后面是对象,单独按是粘贴。读出声就不会混:「删·内·段落」。
| 句子 | 含义 | 什么时候用 |
|---|---|---|
| dip | 删本段(可能残留一个空行) | 清掉一段注释、一段废弃逻辑 |
| dap | 删本段 + 段尾空行 | 同上,但要求删完排版干净(更常用) |
| vip | 选中本段 | 第 7 章可视化模式预告:对象同样能当选择的名词 |
| yip | 复制本段 | 把一个代码块整体搬走 |
段落对象和 i{ 的分工:函数体有花括号包着,用 i{ 更精确;没有统一包裹符的「一坨」(连续的 import、一段 markdown、一串配置项),用 ip。有壳用壳,没壳用段。
段落对象还有一个被低估的用法:阅读时按 ]} 或大写 } 跳到下一段(后者是第 3 章的移动,这里只借个概念)—— 空行分段的代码,一段往往是一个完整的小意图,按段阅读比按行阅读快得多。把「段」当作代码的段落符号,是 ip 带给你的副产品直觉。写 markdown 的人更会立刻爱上它:一个自然段就是一个对象,cip 重写整段,dap 删掉整段连空行,再也不用 V 然后数行。
7. 标签对象:it 与 at(前端福音)
写 HTML / JSX / Vue 模板的同学专享:t = tag。it 是标签里面的内容(不含开闭标签),at 是整个元素(含标签):
cit(change inner tag)可能是前端 Vim 用户按得最爽的键:改标题、改按钮文案、改链接文字,全是它 —— 标签结构毫发无损,内容就地重写。dat 则删掉整个元素,相当于 IDE 里「删除标签」的重构,但不用碰菜单。嵌套标签同样遵守「最内层优先」:光标在 <span> 里,cit 改的是 span 的内容;想改外层 <div> 的?先把光标挪到 span 外面(div 内)再按。
<br/>、<img/> 这类自闭合标签没有内容,it 无从谈起;at 倒是可以删掉它。另外 Vim 的标签匹配是朴素正则,遇到 JSX 里大小写组件、展开属性写得很长的标签,偶尔会认错边界 —— 常规场景放心用,怪异结构用 vat 先看一眼选中范围再决定删不删(第 7 章)。
8. 对象不分行:跨行的括号一样罩得住
「一行放不下拆成多行的参数列表」是格式化后的常态。文本对象对此毫无怨言 —— 它按的是结构,不是行:
四行的调用,di( 一键清成两行;换成 ci( 还能直接重写。对比一下没有对象的年月:光标挪到第一个参数、djj 或者按三次 dd 还得防多删 —— 现在你只说「清空括号」,几行是括号自己的事。配合第 3 章会讲的 %(配对跳转),配对结构在 Vim 里处处是VIP。
跨行能力也让对象成了格式化不友好代码的救星:IDE 一开自动换行,行级操作(按行数删、按列选)经常失手,但结构不变 —— 括号还是那对括号,字符串还是那个字符串。养成习惯:想动「结构里的东西」,永远先找对象,行数交给机器。
9. 名词不变,动词随便换
本章你其实一直在用三个动词(d 删、c 改、y 复制),但注意:我们从没为某个动词单独学过对象。词典是一本,菜单随便点:
| 动词 ↓ / 对象 → | iw | i( | i" | it |
|---|---|---|---|---|
| d 删 | diw 删词 | di( 清参数 | di" 清字符串 | dit 清标签内容 |
| c 改 | ciw 改词 ⭐ | ci( 重写参数 ⭐ | ci" 改文案 ⭐ | cit 改标签内容 ⭐ |
| y 复制 | yiw 抓词 | yi( 抓参数 | yi" 抓字符串 | — |
这张 3×4 的矩阵就是 12 条命令,再加 a 版本直接翻倍 —— 而你要背的只有「两个边界字母 + 五六个对象字符」。下一章我们把动词全表补齐(还有 p 粘贴、r 单换、大小写、缩进……),并把这些零件组装成完整的语法公式。届时你会发现:这一章的词典,一个字都不用重学。
10. 每日训练计划(30 分钟 × 3 天)
| 天 | 时间盒 | 内容 | 通过标准 |
|---|---|---|---|
| 第 1 天 | 0–10 分钟 | 重放演示 1–2,读 §1–§3;靶场里 diw/daw/ciw 各 10 次 | 能脱口说出 i 和 a 删完的差别 |
| 10–30 分钟 | 练习 1:词敏感训练 | 光标故意放词中间,次次删干净 | |
| 第 2 天 | 0–10 分钟 | 重放演示 3–5,读 §4–§5;括号/引号三件套各 10 次 | ci( 与 ci" 不假思索 |
| 10–30 分钟 | 练习 2 + 练习 3 | 嵌套换挡成功 8/10 次 | |
| 第 3 天 | 0–10 分钟 | 重放演示 6–9,读 §6–§9 | 能背出四族对象各自的场景 |
| 10–30 分钟 | 练习 4:盲测毕业考 | 9 题对 7 题,本章毕业 |
11. 如果本章只记住三件事
- i 是里面,a 是带壳(还多吃一口空白):删完留不留「壳」和双空格,选前想一秒;
- 光标进去就行:词内、括号内、引号内 —— 任意位置,对象自动框住整个结构,嵌套取最内层;
- 词典一本,动词随便点:d/c/y × iw/i(/i"/it 的矩阵,先把三颗 ⭐(ciw、ci(、ci")练成本能,其余随用随查。
下一章把动词讲全,组装出完整的语法公式 —— 到那时,你按的每一组键,都是一句完整的中文。
练习
练习 1:词敏感训练(10 分钟)
靶场找 20 行代码,光标故意停在标识符的中间字母上,然后:①diw 删掉、u 悔回;②daw 删掉、观察双空格消失、u 悔回;③ciw 输入 tmp、Esc、u 悔回。每个词三连,做 20 组。目标:手指记住「词中间也能整词操作」的安全感。
练习 2:括号全家桶 + 嵌套换挡(10 分钟)
找一个有两层嵌套花括号的函数(或自己造一个),光标放进最内层:di{ → u;k 挪出内层 → 再 di{(这次是外层)→ u。然后对函数调用练 di( / ci((重打一遍原参数) / da(,各 5 次,每次光标换一个奇怪的位置起手。通过线:10 次里 8 次一次到位、不用 u。
练习 3:ci" 文案马拉松(10 分钟)
找一段有 10 个以上字符串的代码(日志、报错、UI 文案都行),逐个:不移动光标到引号上(在字符串内部任意字符即可),ci" → 随便改两个字 → Esc。10 个为一组,做完 u 回滚所有修改。进阶:换成 da" 感受「连壳端走」,再 u。
练习 4:盲测毕业考(10 分钟)
准备好纸笔(或让同事出题),对着一屏真实代码,以下 9 个任务先说按键、再看答案:①删光标所在的词(不留双空格)②改光标所在的词 ③清空某函数调用的参数 ④重写参数列表 ⑤删掉某字符串内容(留引号)⑥删掉某字符串整体(连引号)⑦改 h2 标签里的文案 ⑧删掉空行隔开的一段注释 ⑨复制括号里的全部参数。9 题对 7 题毕业。答案:daw / ciw / di( / ci( / di" / da" / cit / dap / yi(。
常见陷阱
()、「」、"" 这些全角字符 Vim 不认 —— di( 找的是半角 ()。写中文文案、改全角引号时,对象会失灵,这是少数该回到可视化模式或替换命令的场景(第 7、8 章)。代码本体几乎全是半角,日常不受影响,但要有这个预期。
>、泛型 <T> 混在组件里的场景,边界偶尔认错。防御性习惯:先 vat(第 7 章)看高亮范围,确认后再下手;拿不准就退回 di{ 或行级操作。
VSCode / IDEA 差异
VSCode(VSCodeVim)
- 文本对象原生全支持,包括 it/at 标签对象(JSX 亦可一试)
- 配对高亮(
editor.matchBrackets,默认开)是肉眼找块的好帮手 —— 但记住按对象时不需要先看高亮 - 想给对象加「改包围符」能力(如把
"换'),装 Vim 插件的 surround 扩展即可,第 12 章配置里讲 - VSCode 原生的「扩展选定」(Ctrl+Shift+→)与 ai{ 是同赛道:前者鼠标脑,后者语言脑 —— 练后者
IntelliJ IDEA(IdeaVim)
- IdeaVim 支持 iw/i(/i{/i" 全家;it 标签对象近年版本也已内置(老版本需在 .ideavimrc 确认无禁用)
- IDEA 自带的结构化选择(Cmd+W 扩大选择 / Alt+Cmd+T Surround With)与文本对象功能重叠 —— 建议:编辑动作用 Vim 对象,重构类操作(包裹 if/try)用 IDE 菜单,各吃各的长处
- IDEA 的语法感知比 Vim 朴素匹配强:JSX 复杂标签上,IDEA 自己的高亮可以帮你预判 it 的边界
- 遇到对象失灵(尤其嵌套模板字符串),先检查光标位置,再考虑用 IDEA 的结构选择兜底
本章速记
| 按键 | 作用 | 按键 | 作用 |
|---|---|---|---|
| i / a | 里面 / 周围(含边界+空白) | ciw | 改光标所在的词 ⭐ |
| iw / aw | 词 / 词+尾部空格 | ci( | 重写括号内(参数)⭐ |
| i( i{ i[ | 各括号的内部 | ci" | 改字符串内容 ⭐ |
| a( 等 | 连括号一起(拆壳) | cit | 改标签内容(前端) |
| ip / ap | 段落 / 段落+尾空行 | dap | 删一整段(干净) |
一句话心法:i 是里面,a 是带壳 —— 想作用于「这个东西」,先喊它的名字,光标在屋里就行。