协同编辑基础:多标签页实时同步

目标

协同编辑(多人同时改)是编辑器皇冠上的明珠。本节课先用 BroadcastChannel 做"同浏览器多标签页实时同步",建立"编辑=广播状态"的直觉;再点明真实协同(OT/CRDT)要解决的核心难题。

运行环境:浏览器(开两个标签页指向同一文件)。
运行方式:在 l1 的 index.html 里,把 <script> 换成下面内容,打开两个标签页,在一个里输入,另一个会同步。

<script>
  const editor = document.getElementById('editor')
  const channel = new BroadcastChannel('rte-sync') // 同源标签页间的通信通道
  // 收到别的标签页发来的内容 -> 应用
  channel.onmessage = (e) => {
    if (document.activeElement !== editor) editor.innerHTML = e.data
  }
  // 本标签页内容变化 -> 广播
  editor.addEventListener('input', () => {
    channel.postMessage(editor.innerHTML)
  })
</script>

两个标签页通过 rte-sync 频道互发 HTML,实现"一处改、处处变"。这是"状态广播"的最简形态。

名词解释

BroadcastChannel:同源下不同标签页/Worker 之间的消息通道,postMessage 发、onmessage 收。适合做同浏览器多实例同步。
协同编辑(Collaborative Editing):多人同时编辑同一份文档且最终一致。难点是"两人同时改同一处"如何合并。
OT / CRDT:协同合并的两大算法族。OT(操作变换)需中心服务器协调;CRDT(无冲突复制数据类型)让各端独立合并也不冲突(如 Yjs 用的就是 CRDT)。

课后练习

练习:上面的同步是"整篇 HTML 覆盖",两人同时输入会互相覆盖。请说明真实协同用什么思路避免"后到的覆盖先到的"。

答案:真实协同不传"整篇结果",而是传"编辑操作(在哪个位置插了什么 / 删了哪段)"。当两人的操作同时到达,用 OT 把操作互相变换、或 CRDT 直接可交换合并,保证各端最终得到同一份内容,而不是简单覆盖。本课广播的是结果,仅用于演示"状态要同步"这一前提。

总结

协同编辑听起来高大上,本质是先建立"编辑=产生变更、变更要广播、各端合并"的闭环。本课用 BroadcastChannel 把"同步"这件事跑通了,但它传的是"整篇结果",所以两人同时打字会互相覆盖——这只是演示。真正的协同(Google Docs、飞书文档)传的是"操作"而非"结果":你插了一个字,广播的是"在第 5 位插 A";对方同时插字,算法把两条操作变换/合并,最终两边一致。OT 靠服务器当裁判,CRDT(如 Yjs)让各端数学上必然收敛。理解本课,你就站在了协同编辑的门口;下一步是把"广播结果"升级成"广播操作 + 冲突合并"。