让我们继续探究富文本编辑器ProseMirror。

文档切片

继续引用来自doc.indexing的例子:

0   1 2 3 4    5
 <p> O n e </p>

5            6   7 8 9 10    11   12            13
 <blockquote> <p> T w o <img> </p> </blockquote>

正是由于ProseMirror可以用扁平化的索引来表示文档内容,这让切割文档变得容易多了。只要给出起始位置的索引,以及结尾位置的索引,就可以获得文档的一部分切片(slice)。上面的例子文档索引从0到13,我们可以从2切到8,结果如下:

2 3 4    5
 n e </p>

5            6   7 8
 <blockquote> <p> T 

但是,这明显产生了一个问题:有的元素被切开了!比如上面索引为4的</P>元素没有了对应的开始标签,然后索引为6的<p>元素没有了对应的结束标签。这在HTML里面可是不允许的,于是要对其进行补全:

   2 3 4    5
<p> n e </p>

5            6   7 8
 <blockquote> <p> T </p>

但是补全的标签不属于这个文档切片,所以不能算在其的长度里面。事实上,ProseMirror的文档切片,也就是Slice类型,有两个属性用来表示这些个补全的标签:

  • openStart,用来表示多少个标签被补在了切片之前
  • openEnd,用来表示多少个标签被补在了切片之后

immutable

谈到切片,顺便谈一谈immutable(不可变)的数据类型。ProseMirror大量使用JavaScript的Array(数组)来存储文档内容。如果要获取一部分文档内容,其实就是从Array从切割一部分出来,使用的是Array.prototype.slice()方法。而这个方法执行的是浅拷贝(shallow copy):

let fruits0 = ['apple', 'orange', 'banana']
let fruits1 = fruits0.slice(1,2)
console.log(fruits1) // 输出 ["orange"]
console.log(fruits0) // 输出 ["apple", "orange", "banana"]

也就是说,使用slice()方法从一个JavaScript数组中切出一部分元素,会形成一个新的数组,但是原来数组里面的元素并不会发生变化,因为数组存储的元素的是引用,新数组和旧数组里面存储的引用指向相同的元素。

所以在ProseMirror中许多用数组表示的数据结构中,你可以增加一个元素,或者删除一个元素,但是千万不能修改元素的具体内容,否则会影响先前数据结构中存储的内容。

其他

JavaScript中许多类型都是从Object派生出来的对象,并且只能以引用的方式来使用。从一个对象创建一个新的和原来不相干的对象其实很不容易,例如上文提到的Array.prototype.slice()就只是执行浅拷贝。如果想要执行深拷贝(deep copy),一个简单的方法是使用ES6新增的Object.assign()方法:

const obj = {a:1, b:2}
const obj1 = Object.assign({c:3}, obj)
console.log(obj1) // 输出 {c: 3, a: 1, b: 2}

更多请参考:

(未完待续)