命令式加粗:不用 execCommand 手写 Bold
目标
document.execCommand('bold') 已废弃、且生成标签不可控。本节课手写 toggleBold():用 Range 把选中的内容取出来、包一层 <strong>,再放回去。
运行环境:浏览器。运行方式:在 l1 的 index.html 里,把 <script> 换成下面内容,选中文字后点"加粗"按钮。
<script>
const editor = document.getElementById('editor')
function toggleBold() {
const sel = window.getSelection()
if (!sel.rangeCount || sel.isCollapsed) return // 没选中就不处理
const range = sel.getRangeAt(0)
const bold = document.createElement('strong')
bold.appendChild(range.extractContents()) // 把选中内容"抽"出来
range.insertNode(bold) // 包一层 strong 放回去
sel.removeAllRanges() // 收起选区,避免残影
}
document.getElementById('bold').addEventListener('click', toggleBold)
</script>配套加一个按钮 <button id="bold">加粗</button>。extractContents() 会剪切选区内容,insertNode 把包裹后的节点塞回原位——这就是"命令式格式"的最小骨架。
名词解释
extractContents():Range 的方法,把选区范围内的节点"剪切"出来,返回一个 DocumentFragment。
insertNode(node):把节点插入到 Range 的起点位置。
命令式格式(Imperative Formatting):直接操作 DOM 来改格式(加粗=包一层标签),与"声明式"(改数据模型、由框架重渲染)相对。execCommand 是浏览器内置的命令式,已废弃。
课后练习
练习:仿照 toggleBold,写一个 toggleItalic() 用 <em> 包裹选中内容。
答案:
function toggleItalic() {
const sel = window.getSelection()
if (!sel.rangeCount || sel.isCollapsed) return
const range = sel.getRangeAt(0)
const em = document.createElement('em')
em.appendChild(range.extractContents())
range.insertNode(em)
sel.removeAllRanges()
}总结
手写 toggleBold 是"去 execCommand"的第一课。你会发现它比想象中微妙:extractContents() 如果跨了多个元素(比如选中跨两段的文字),会把中间结构切碎,需要处理边界。这正是生产编辑器不直接操作裸 DOM 的原因——它们先在统一的数据模型里做"标记一段加粗",再一次性算出该生成什么 DOM。但亲手写一遍的价值在于:你彻底理解了"加粗"在 DOM 层面就是"用 <strong> 把一段内容包起来"。理解了最小骨架,再看 ProseMirror 的 addMark 就不会觉得是黑魔法。