JSON.stringify()只序列化对象自身可枚举属性,忽略原型链、不可枚举属性、函数、Symbol、undefined及循环引用,这是ECMA-404规范行为;需通过toJSON方法、手动提取原型数据或改用structuredClone等方案解决。

JSON 序列化(JSON.stringify())只处理对象自身的可枚举属性,自动忽略原型链上的属性、不可枚举属性、函数、Symbol 键、undefined 和循环引用。这不是 bug,而是规范行为——但常导致意外数据丢失。
理解为什么原型属性不出现
JSON 标准仅定义键值对的纯数据结构,不支持类、继承或运行时行为。JavaScript 的 JSON.stringify() 严格按 ECMA-404 规范执行:遍历对象自身 for...in 可枚举属性(且 hasOwnProperty 为 true),跳过所有原型链属性。即使你用 Object.create() 或 class 继承添加方法或默认字段,它们都不会进入 JSON 字符串。
显式提取原型链属性(需谨慎)
若业务确实需要序列化原型上定义的“数据型”属性(如基类中声明的默认配置字段),不能依赖自动遍历,而应主动收集:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
- 使用
Object.getPrototypeOf(obj)逐层向上获取原型对象 - 用
Object.getOwnPropertyNames()获取每个原型上的所有自有属性名(含不可枚举) - 过滤出你需要的、非函数、非 Symbol、非 undefined 的值,并合并到待序列化对象中
- 注意避免重复覆盖、无限递归(如 Object.prototype 原生方法)和污染原始对象
示例片段:
function serializeWithPrototype(obj) {
const result = {};
let current = obj;
while (current && current !== Object.prototype) {
Object.getOwnPropertyNames(current).forEach(key => {
const desc = Object.getOwnPropertyDescriptor(current, key);
if (desc && desc.enumerable && typeof desc.value !== 'function') {
if (!(key in result)) result[key] = desc.value;
}
});
current = Object.getPrototypeOf(current);
}
return JSON.stringify(result);
}
更推荐的替代方案
与其修补 JSON 行为,不如从设计层面规避问题:
-
数据与行为分离:把需要持久化的字段全部放在实例自身(如构造函数中赋值
this.id = ...),原型上只放方法 -
实现
toJSON()方法:在类中定义该方法,返回一个干净的 plain object,明确控制哪些字段参与序列化 -
用结构化克隆或第三方序列化库:如
structuredClone()(现代环境支持)、flatted(支持循环引用)、serialize-javascript(侧重函数/Date 等) - 服务端约定优先:前后端协商固定字段结构,前端构造数据时直接填充,不依赖原型推导
调试时快速验证属性来源
遇到序列化结果不符预期,可快速定位属性归属:
-
obj.hasOwnProperty('prop')→ 返回 true 表示是实例自身属性 -
'prop' in obj→ true 表示自身或原型链上有该属性 -
Object.getPrototypeOf(obj).hasOwnProperty('prop')→ 判断是否来自直接原型 - 浏览器控制台中展开对象,灰色字段通常来自原型(Chrome/Firefox 中有视觉提示)

















