网站首页 > 技术文章 正文
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。
JSON.stringify 是一个用于序列化数据的核心 JavaScript API,其性能直接影响着 Web 上的常见操作,例如:为网络请求序列化数据或者将数据保存到 localStorage。
const userData = {
name: "Alice",
age: 25,
email: "alice@example.com",
hobbies: ["reading", "traveling", "coding"],
};
document.getElementById("saveToLocalStorage").addEventListener("click", () => {
try {
const serializedData = JSON.stringify(userData);
// 将序列化的数据保存到 localStorage
localStorage.setItem("userData", serializedData);
} catch (error) {}
});
// 发送到服务器
document.getElementById("sendToServer").addEventListener("click", () => {
try {
// 使用 JSON.stringify 序列化数据并发送到服务器
const serializedData = JSON.stringify(userData);
fetch("https://example.com/api/saveUserData", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: serializedData,
})
});
因此,更快的 JSON.stringify 意味着更快的页面交互和更灵敏的应用程序响应速度。 2025 年 8 月 4 日,官方宣布最近的 V8 中的 JSON.stringify 速度提高了两倍以上。
1.无副作用的快速路径
这项优化的基础是一条新的快速路径,其建立在一个简单的前提上,即如果能够保证序列化对象不会触发任何副作用,那么就可以采用一种速度更快、更专业的实现。
// 示例数据对象
const safeObject = {
name: "Alice",
age: 25,
email: "alice@example.com",
hobbies: ["reading", "traveling", "coding"],
metadata: {
createdAt: "2023-10-01",
updatedAt: "2023-10-10",
},
};
// 可能导致副作用的对象
const unsafeObject = {
name: "Bob",
age: 30,
email: "bob@example.com",
get hobbies() {
console.log("Accessing hobbies...");
return ["gaming", "cooking"]; // 访问器属性可能触发副作用
},
toJSON: function () {
console.log("Custom serialization logic...");
return { ...this, customField: "This is a side effect!" }; // 自定义序列化逻辑
},
};
// 序列化函数
function serializeObject(obj) {
try {
const startTime = performance.now(); // 开始计时
const serialized = JSON.stringify(obj);
const endTime = performance.now(); // 结束计时
console.log(`Serialized object in ${endTime - startTime} ms`);
console.log("Serialized data:", serialized);
} catch (error) {
console.error("Serialization failed:", error.message);
}
}
// 测试安全对象
console.log("Serializing safeObject:");
serializeObject(safeObject); // 安全对象不会触发副作用
// 测试不安全对象
console.log("\nSerializing unsafeObject:");
serializeObject(unsafeObject); // 不安全对象可能触发副作用
此处的 “副作用” 是指任何破坏对象遍历的因素,不仅包括在序列化中执行用户定义代码等情况,还包括可能触发垃圾回收周期的更细微的内部操作。
只要 V8 能够确定序列化不会受到影响,其就可以继续使用这条高度优化的路径,从而绕过通用序列化器所需的许多高开销检查和防御性逻辑,从而显著提升纯数据的处理速度。
同时,与递归的通用序列化器相比,新的快速路径是迭代的。该架构不仅消除了堆栈溢出检查的需要并允许开发者在编码更改后快速恢复,还允许开发者序列化比以前更深的嵌套对象。
2.处理不同的字符串表示形式
V8 中的字符串可以用单字节或双字节字符表示
- 如果字符串仅包含 ASCII 字符,则在 V8 中存储为单字节字符串
- 如果字符串至少包含一个 ASCII 范围之外的字符,则该字符串的所有字符都将使用双字节表示,此时内存使用率翻倍
const asciiString = "Hello, world! 123";
// 仅包含 ASCII 字符
console.log("ASCII String:", asciiString);
const nonAsciiString = "你好,世界!";
// 包含非 ASCII 字符
console.log("Non-ASCII String:", nonAsciiString);
为了避免统一实现中不断出现的分支和类型检查,整个字符串生成器现在基于字符类型进行了模板化,即内部编译了两个不同的专用序列化器版本:
- 完全针对单字节字符串进行优化的版本
- 针对双字节字符串进行了优化的版本,此时虽然会影响二进制大小,但在一定程度上确实能提升性能
该实现可以高效地处理混合编码,但是在序列化过程中,必须检查每个字符串的实例类型,以检测无法在快速路径上处理的表示形式。而这些表示形式需要回退到慢速路径,这项必要的检查还能揭示字符串使用的是单字节还是双字节编码。
因此,从单字节字符串化器切换到双字节版本的决定本质上是无代价的。当现有的检查显示是双字节字符串时,就会创建一个新的双字节字符串化器,并继承当前状态。最后,只需将初始单字节字符串化器的输出与双字节字符串化器的输出连接起来,即可构建最终结果。这种策略确保针对常见情况保持高度优化,同时向处理双字节字符的过渡既轻量又高效。
3.使用 SIMD 优化字符串序列化
JavaScript 中的任何字符串都可能包含序列化为 JSON 时需要转义的字符(例如: " 或 \),而传统的逐字符循环查找这些字符的速度很慢。
// 示例字符串,包含需要转义的字符
const rawString =
'This is a "test" string with special characters: \\, \n, and \t.';
// 方法 1:传统的逐字符循环查找并转义
function escapeStringTraditional(input) {
let result = "";
for (let i = 0; i < input.length; i++) {
const char = input[i];
switch (char) {
case '"':
result += '\\"';
break;
case "\\":
result += "\\\\";
break;
case "\n":
result += "\\n";
break;
case "\t":
result += "\\t";
break;
default:
result += char;
}
}
return result;
}
// 方法 2:使用 JSON.stringify 自动转义
function escapeStringOptimized(input) {
// JSON.stringify 会自动转义特殊字符
return JSON.stringify(input).slice(1, -1); // 去掉首尾的引号
}
新版本的 V8 引入了两级策略:
- 对于较长的字符串切换到专用的硬件 SIMD 指令,例如: ARM64 Neon
- 对于较短的字符串,由于硬件指令的设置成本过高,采用一种称为 SWAR(寄存器内 SIMD)的技术。这种方法在标准通用寄存器上使用巧妙的按位逻辑,以非常低的开销一次性处理多个字符
无论采用哪种方法,该过程都非常高效:可以快速地逐个块扫描字符串。如果没有块包含任何特殊字符,可以简单地复制整个字符串。
4.更快的双精度转字符串算法
将数字转换为其字符串表示形式是一项极其复杂且性能至关重要的任务。新的 V8 通过升级了核心 DoubleToString 算法来显著加快此过程。现在,已将长期使用的 Grisu3 算法替换为 Dragonbox,用于实现最短长度的数字到字符串的转换。
const numbers = [
123, // 整数
123.456, // 浮点数
0.0000123, // 小数值
1.23e30, // 大指数值
-456.789, // 负数
Math.PI, // 常见数学常量
Number.MAX_SAFE_INTEGER, // 最大安全整数
Number.MIN_VALUE, // 最小正值
];
// 使用 toString 方法将数字转换为字符串
console.log("Using Number.prototype.toString():");
numbers.forEach((num) => {
console.time(`toString(${num})`);
const str = num.toString();
console.timeEnd(`toString(${num})`);
console.log(`${num} -> "${str}"`);
});
// 使用 JSON.stringify 方法序列化数字
console.log("\nUsing JSON.stringify():");
numbers.forEach((num) => {
console.time(`JSON.stringify(${num})`);
const str = JSON.stringify(num);
console.timeEnd(`JSON.stringify(${num})`);
console.log(`${num} -> "${str}"`);
});
虽然这项优化是由 JSON.stringify 驱动的,但新的 Dragonbox 实现使整个 V8 引擎中所有对 Number.prototype.toString() 的调用都受益,即任何将数字转换为字符串的代码(而不仅仅是 JSON 序列化)都会受益。
5.局限性
新的快速路径(New Fast Path)通过专门处理常见、简单的情况来提升其速度。如果被序列化的数据不满足指定条件,V8 会回退到通用序列化器以确保正确性。为了获得最佳性能,JSON.stringify 调用必须遵循以下条件。
- 无replacer或space参数:提供 replacer 函数或用于美观打印的 space/gap 参数是通用路径独有的功能。而快速路径专为紧凑、非转换序列化而设计。
- 普通数据对象和数组:被序列化的对象应为简单的数据容器,这意味着自身及其原型不得包含自定义的 .toJSON() 方法。快速路径假定标准原型(例如 Object.prototype 或 Array.prototype)没有自定义序列化逻辑。
- 对象上无索引属性:快速路径针对具有常规字符串键的对象进行了优化。如果对象包含类似数组的索引属性(例如:“0”、“1”等),则将由速度较慢、更通用的序列化器处理。
参考资料
https://v8.dev/blog/json-stringify
猜你喜欢
- 2025-09-06 每天一个 Python 库:json 数据读写技巧合集,实战最常用!
- 2025-09-06 Python 使用 JsonPath 完成接口自动化测试中参数关联和数据验证
- 2025-09-06 String 类型和 Hash 类型的结构比较
- 2025-09-06 50 道高频 JavaScript 面试题,从基础到进阶 (附答案)
- 2025-09-06 99% 的人都遇到的 JSON 错误, jsonrepair 修复一气呵成!
- 2025-09-06 零基础入门AI智能体:详细了解什么是变量类型、JSON结构、Markdown格式
- 2025-09-06 36. JSON 解析速成,三库实战揭秘
- 2025-06-23 Java中玩转JSON:让数据交互变得简单又有趣
- 2025-06-23 爬虫逆向学习-下载网易云音乐(爬虫逆向分析)
- 2025-06-23 一篇长文带你在Python里玩转Json数据
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)