#优质博文 #前端 #JavaScript #编码问题 #字符串处理
JavaScript string slice() considered harmful | Attio

AI 摘要:本文探讨了 JavaScript 中 string.slice() 方法在处理 Unicode 字符串时可能导致的问题,特别是在处理包含表情符号等复杂字符时会意外截断代码单元,导致数据传输和解码错误。通过一个实际案例(Attio 的 CSV 导入工具与 Google Spanner 数据库交互时出现的 gRPC 错误),作者深入分析了 JavaScript 字符串的 UTF-16 编码机制、代码点和代码单元的区别,以及如何使用更安全的方法(如基于代码点的迭代)来避免这类问题,最终提供了解决方案并修复了错误。

1. 引言:CSV 导入工具的背景与问题
• 讲述了 Attio 的 CSV 导入工具每月处理大量数据,数据来源多样且不可预测,容易遇到边缘情况。
• 发现了一个 gRPC 错误,涉及 Google Spanner 数据库,错误提示请求协议无效。

2. 问题复现与初步分析
• 通过日志查询和客户提供的 CSV 文件复现了问题。
• 在调试过程中,注意到一个字符串截断函数 truncateSortableValue ,使用 slice() 方法截断字符串。
• 发现当 slice() 截断到表情符号(如 🇬🇧)中间时,得到的是不完整的代码单元,导致后续错误。

3. 深入理解 JavaScript 字符串与 UTF-16 编码
• 介绍了 JavaScript 字符串基于 Unicode 和 UTF-16 编码,解释了代码点(code point)和代码单元(code unit)的概念。
• 指出 UTF-16 使用 16 位代码单元存储字符,常见字符占一个单元,复杂字符如表情符号可能占两个单元。
• 讨论了字形簇(grapheme cluster),如表情符号由多个代码点组合而成,截断时需特别注意。
• 强调 string.slice() 是基于代码单元操作,而非代码点,可能导致截断不完整字符,影响数据编码(如传输到数据库时的 UTF-8 编码问题)。

4. 问题根源:编码与解码失败
• 分析了将不完整代码单元编码为 UTF-8(如使用 Buffer.from 或 protobufjs)时,解码端无法正确还原字符串,导致错误。
• 虽然无法直接调试 Google Spanner 的闭源代码,但通过示例证明了这种编码失败是问题的根本原因。

5. 解决方案:基于代码点的安全截断
• 提出了使用 [Symbol.iterator]() 或扩展运算符来基于代码点而非代码单元进行字符串操作。
• 提供了一个新函数 safeHead(),安全地获取字符串前 n 个代码点,避免中途截断代码单元。
• 修改了原截断函数 truncateSortableValue,使用新方法解决问题,最终通过拉取请求修复了导入工具的错误。


author Attio 团队 James Mulholland
JavaScript string slice() considered harmful | Attio
 
 
Back to Top