POJO(Plain Old JavaScript Object)是什么

原文:What is a Plain Old JavaScript Object (POJO)?

POJO是JavaScript存储用户输入数据最常用的方式,如何确切定义POJO呢?

关于JavaScript的POJO是什么有很多讨论:StackOverflow上有人认为任何包含用户数据的类都是POJO,而一个流行的npm包把POJO定义为原型是Object.prototype的任何对象。

直观的看待POJO,它其实是只包含数据的对象,没有方法和内部状态。大部分JavaScript代码将用花括号{}创建的对象视为POJO。而有些更严格的代码会Object.create(null)来创建POJO,避免从内置的Object类继承其他属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 如果用`{}`创建对象`obj`,`obj`是`Object`类的实例
// 它有一些内置的属性
let obj = {};
obj.hasOwnProperty; // [Function]
obj.constructor === Object; // true

// 另一种方式,`Object.create(null)`创建的对象
// 不从**任何**类继承
obj = Object.create(null);
typeof obj; // 'object'
obj.hasOwnProperty; // undefined
obj.constructor; // undefined

obj.prop = 42;
obj.prop; // 42

POJO和Map

JavaScript的Map可以替代POJO用于存储数据,因为它不从Object类继承属性。然而对象比Map更容易操作,因为并非所有的JavaScript函数、框架和库都支持Map。例如JSON.stringify()函数不支持序列化Map

1
2
const map = new Map([['answer', 42]]);
JSON.stringify(map); // '{}'

检查对象是否POJO

检查一个对象是否是POJO有一些棘手,还要考虑Object.create(null)创建的对象是不是POJO。最安全的方法是用Object.getPrototypeOf()函数比较对象的原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function isPOJO(arg) {
if (arg == null || typeof arg !== 'object') {
return false;
}
const proto = Object.getPrototypeOf(arg);
if (proto == null) {
return true; // `Object.create(null)`
}
return proto === Object.prototype;
}

isPOJO({}); // true
isPOJO(Object.create(null)); // true
isPOJO(null); // false
isPOJO(new Number(42)); // false

例如,以下是Mongoose内部的isPOJO()函数

1
2
3
4
5
6
7
8
9
10
exports.isPOJO = function isPOJO(arg) {
if (arg == null || typeof arg !== 'object') {
return false;
}
const proto = Object.getPrototypeOf(arg);
// `Object.create(null)`创建的对象prototype是null
// 检查`proto`的constructor是安全的
// 因为`getPrototypeOf()`已经打破了对象数据和对象元数据的边界
return !proto || proto.constructor.name === 'Object';
};

Mongoose没有用proto.constructor === Object,而是检查constructor.name属性,这样可以支持Node.js的vm模块生成的对象。

1
2
3
4
const obj = require('vm').runInNewContext('({})');
// `obj`继承于不同JavaScript上下文的`Object`类
obj.constructor === Object; // false
obj.constructor.name; // 'Object'