在讲述字符串的奇技淫巧之前,我们需要先了解 V8 对于 JavaScript String 的表达。
    在 V8 中字符串有如下 5 种表达模式:

    • SeqString
      • 在 V8 堆内使用(类似数组的)连续空间存储字符串。
      • 实际数据存储时分为 OneByte、TwoByte(Unicode)两类。
    • ConsString(first, second)
      • 在字符串拼接时,采用树形结构表达拼接后(first + second)的字符串。
    • SliceString(parent, offset)
      • 在字符串切割时,采用 offset 与 [length] 表达父字符串(parent)的一部分。
    • ThinString(actual)
      • 直接引用另一个字符串对象(actual)。
      • 在多数情况下可以被认为与 ConsString(actual, empty_string) 等价。
    • ExternalString

      • 代表了产生在 V8 堆外的字符串资源。
      • 实际数据表达时分为 OneByte、TwoByte(Unicode)两类。 ThinString 需要开启 flag: —thin_strings 在老版本中最大字符串长度均为 ~268.4M ,而在 V8 新版本(≥ 6.2.4)中最大长度限制增加到 ~1.073B(64 位平台)
    • 32 位平台, 你真的了解js中的字符串吗? - 图1 (仍不变)

    • 64 位平台,你真的了解js中的字符串吗? - 图2 (比起 32 位平台增加了 4 倍) 64 位平台下最大长度:由 kMaxLength (也就是 int32)可以表达的最大正数值 > 你真的了解js中的字符串吗? - 图3 ,减去 1 bit(TwoByteString 表达需占用 2 个字节),再减去 kHeaderSize = 24 得到。 所有的表达模式均有 [length] 属性(由基类 String 定义)记录了字符串中的字符数(UTF-16 单元数),但只有 SeqString 真正存储了字符数据,而 ConsString 、SliceString 、ThinString 均为其它表达模式的引用(不存储字符数据)。
      在实际应用过程中,由于经常发生字符串拼接操作,故 ConsString 经常会被用来表达相应的字符串。 字符串拼接时如果以传统方式(如 SeqString)存储,拼接操作的时间复杂度为 O(n) ,采用 > 绳索结构[Rope Structure] (也就是 ConsString 所采用的数据结构)可以减少拼接所花费的时间。 为了便于理解,我们来看个例子:
      1、创建 2 个英文字符串并拼接它们:
      1. var hello = "hello";
      2. var world = "world";
      3. var hello_world = hello + world;
      你真的了解js中的字符串吗? - 图4
      由于 hello 与 world 中所有字符均为单字节字符,故 V8 采用 SeqOneByteString 来表达它们。
      为了提升短字符串拼接后的访问性能,V8 在这里(hello_world)并不会生成 ConsString 而是直接将 hello 与 world 中的字符拷贝到新建的 SeqOneByteString 中来存储它们。 V8 定义最小 ConsString 长度为 kMinLength = 13 个字符。

    2、创建 1 个中文字符串并与步骤 1 生成的字符串(hello_world)拼接:

    1. var chinese = "你好世界";
    2. var mixed = hello_world + chinese;

    你真的了解js中的字符串吗? - 图5
    由于 chinese 中存在有双字节字符,故 V8 采用 SeqTwoByteString 来表达它。
    拼接后字符串(mixed)超过了最小 ConsString 长度限制,
    生成 mixed = ConsString(hello_world, chinese) 这样的树形结构。

    3、将步骤 2 生成字符串(mixed)进行切割:

    1. var sliced = mixed.slice(1);

    你真的了解js中的字符串吗? - 图6
    由于切割后字符串(sliced)超过了最小 SliceString 长度限制,
    生成 sliced = SliceString(mixed, 1) 这样的空间结构。 V8 定义最小 SliceString 长度也为 kMinLength = 13 个字符。 在这里我们可以看到,只有 SeqOneByteString 与 SeqTwoByteString 实例存储实际的字符,而 SliceString 与 ConsString 实例内部其实只是其它表达模式的引用(不存储字符数据)。
    需要注意的是,在 SliceString 场景中被切割掉的字符并不会被 V8 GC 回收。
    也就是说,例子中 SeqOneByteString 实例中存储的 “h” 字符并不会被 V8 GC 回收。

    总结一下:

    • 字符串在 V8 中采用何种模式表达取决于此字符串的构造方式(Seq 还是 Cons 等)。
    • 字符串拼接时,比起重新分配一个空间进行存储,采用树形结构(ConsString)表达更加高效