判断一个对象是不是“纯对象”看似简单,实际却藏着不少边界情况。很多JavaScript开发者习惯用 typeof obj === 'object' 来区分对象,但数组、null、Date实例、正则表达式、DOM节点等都会返回 object,无法满足精确判断的需求。jQuery.isPlainObject() 正是为了回答“这个对象是否由对象字面量{}或 new Object() 创建”而生。它在jQuery工具方法中地位突出,常被用在 $.extend、深拷贝以及插件参数解析等场景。理解它的判断标准,能有效避免类型误判引发的数据污染和逻辑分支错误。

纯对象与其它对象类型有什么区别
JavaScript中的对象并不都是“普通对象”。从原型角度划分,通过 {} 或 new Object() 创建的对象,其原型直接指向 Object.prototype;而数组、函数、正则、日期等内置类型虽然本质也是对象,但它们的原型链上多出了 Array.prototype、Function.prototype、RegExp.prototype、Date.prototype 等中间层。这意味着它们虽然继承自 Object,却具备各自类型的特殊行为,不能简单视为纯数据容器。
再看自定义构造函数:function Person(name){this.name = name;} 之后 new Person('Tom') 产生的实例虽然可以保存数据,但其原型是 Person.prototype,而不是 Object.prototype。此时如果使用通用对象处理逻辑,可能会遗漏原型上的自定义方法,或者在进行浅合并时错误复制枚举属性之外的内容。纯对象通常只用来承载键值对,不包含额外原型方法,因此很多库会要求传入纯对象以保证行为可预期。
另一个容易混淆的场景是 Object.create(null)。它创建的对象没有原型链,连 hasOwnProperty 等方法都不存在,但jQuery在新版本中将其视为纯对象,因为其本质仍然是数据容器,且更纯粹。这一处理与只判断原型是否直接为 Object.prototype 的方案不同,后续会详细说明。
jQuery.isPlainObject()的实现原理与内部逻辑
jQuery 3.x中的 isPlainObject 源码非常精炼。首先调用 Object.prototype.toString 判断传入值的类型标签是否为 [object Object],如果连这个基本条件都不满足,直接返回 false。这一步可以快速排除 null、数组、函数、字符串等。接着通过内部 getProto 方法获取对象的原型。如果原型为 null,说明是 Object.create(null) 的结果,新版本将其判定为纯对象并返回 true。若原型存在,则继续检查该原型上是否有自己的 constructor 属性,并取出 constructor 函数。最后比较该构造函数的字符串表示是否与全局 Object 函数的字符串表示完全一致。只有完全一致才返回 true。
// jQuery.isPlainObject 简化源码(jQuery 3.x)
function isPlainObject(obj) {
var proto, Ctor;
// 使用 toString 检测类型,排除 null、非对象等
if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") {
return false;
}
proto = Object.getPrototypeOf(obj);
// 没有原型的对象,如 Object.create(null)
if (!proto) {
return true;
}
// 原型上必须有自有 constructor 属性
Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" &&
Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object);
}
// 测试
console.log(isPlainObject({})); // true
console.log(isPlainObject(new Object())); // true
console.log(isPlainObject(Object.create(null))); // true
console.log(isPlainObject(new Date())); // false
console.log(isPlainObject([])); // false
console.log(isPlainObject(function() {})); // false
这里比较构造函数字符串的原因是跨 iframe 或跨窗口环境下,不同 realm 中的 Object 构造函数并不是同一个引用,直接使用 constructor === Object 会导致来自另一个窗口的对象被误判为 false。字符串签名比较则绕开了引用不一致的问题,让判断在主流浏览器中保持稳定。当然,这种做法也无法百分之百覆盖恶意构造的伪造对象,但对于常规开发已经足够。
旧版本 jQuery(如 1.x、2.x)对该方法有过不同实现,有的使用 Object.getPrototypeOf 并与 Object.prototype 做比较,也有的尝试访问 obj.constructor 来判断,但都存在一些边界缺陷。例如 Object.create(null) 曾经返回 false,跨 iframe 对象也会出现偏差。jQuery 3.x 的改进使行为更符合直觉。
实际开发中的应用场景与常见误区
isPlainObject 最常见的应用之一是 $.extend 方法的内部判断。$.extend 在做深浅拷贝时,需要确定目标对象的类型。如果传入的是数组、DOM节点或自定义类实例,复制逻辑会完全不同。例如 $.extend(true, {}, {items: [1,2]}) 时,数组会被当作引用处理还是深拷贝对象,直接影响结果。jQuery 通过 isPlainObject 识别纯对象后,才能决定是否递归合并其键值。
插件开发中也经常使用。一个函数可能接受一组配置项,有时用户直接传配置对象,有时传字符串或 DOM 元素,这时可以先调用 isPlainObject 判断配置项,若不是则构造默认对象再填充。这种防御式代码能减少运行时错误。例如:function init(options){ if(!$.isPlainObject(options)){ options = {}; } this.config = $.extend({}, defaults, options); }。
// 插件参数校验
function setupWidget(options) {
// 不是纯对象时重置为空对象,避免后续合并出错
if (!jQuery.isPlainObject(options)) {
options = {};
}
var settings = jQuery.extend({}, {
color: "blue",
size: "medium"
}, options);
return settings;
}
console.log(setupWidget({ color: "red" })); // 正常合并
console.log(setupWidget([1, 2, 3])); // 数组被忽略,使用默认值
console.log(setupWidget(new Person("Tom"))); // 自定义实例被忽略
常见误区之一是认为 Object.prototype.toString.call(obj) === "[object Object]" 就已经足够判断纯对象。这个判断只能说明 obj 是普通对象类型,但无法区分自定义构造函数的实例。例如 new Person() 的 toString 结果也是 [object Object],但它的原型并不是 Object.prototype。因此仅靠 toString 标签会造成误判。
另一个误区是将 instanceof Object 当作万能检测。instanceof 检查的是原型链上是否出现过 Object.prototype,数组、函数、Date 实例都会返回 true。而且跨 iframe 时,来自其他窗口的对象使用当前窗口的 Object 做 instanceof 判断会返回 false,产生假阴性。isPlainObject 通过字符串比较绕开了这一问题,虽然也不是完全跨库通用,但在 jQuery 内部足够可靠。
与现代JavaScript原生方案的对比与替代
如果你不希望依赖 jQuery,也可以用原生方法实现类似 isPlainObject 的功能。在同一个 realm 内,最简单的判断是:
function isPlainObjectNative(obj) {
if (obj === null || typeof obj !== "object") {
return false;
}
var proto = Object.getPrototypeOf(obj);
return proto === Object.prototype || proto === null;
}
console.log(isPlainObjectNative({})); // true
console.log(isPlainObjectNative(Object.create(null))); // true
console.log(isPlainObjectNative([])); // false
console.log(isPlainObjectNative(new Date())); // false
console.log(isPlainObjectNative(Object.create({}))); // false
这个方法在大部分单窗口应用中可以良好工作,但它没有处理跨 iframe 场景。若你的页面嵌入了多个 iframe,并且需要在父窗口判断来自 iframe 的对象,上述函数会因为 Object.prototype 引用不同而返回 false。此时可以借鉴 jQuery 的思路,比较构造函数的 toString 结果。不过如果你的应用不涉及跨窗口对象,使用原生方法更轻量,也能避免引入整个 jQuery 库。
此外,lodash 也提供了 _.isPlainObject 方法,其实现思路与 jQuery 类似,同样使用 Object.prototype.toString 和原型链检查,并处理了跨 realm 情况。如果你在项目中已经使用 lodash,可以直接调用它,不必自己编写。但了解其原理有助于在代码审查或迁移时做出合理选择。
需要特别注意的是,ES6 的 class 语法创建的实例也不是纯对象,因为 class 本质上仍是构造函数,实例的原型指向类的 prototype。例如 class Foo {}; new Foo() 经过 isPlainObject 判断会返回 false。这与直觉一致:它承载了类逻辑,不应被普通对象工具处理。而 Object.create(Object.prototype) 创建的对象虽然原型直接是 Object.prototype,会被判定为 true,这也没有问题,因为行为与 {} 一致。
使用isPlainObject时需要注意的性能与兼容性细节
isPlainObject 内部需要调用 Object.prototype.toString、获取原型、可能还包含函数字符串化对比。与简单的 typeof 判断相比,开销略高,但在绝大多数业务代码中不会成为瓶颈。如果你在循环或高频路径中大量调用该方法,可以考虑缓存结果或预先做类型白名单。将 isPlainObject 用于深层递归拷贝时,每一层对象都会触发一次判断,这在处理超大对象图时可能累积成可感知的耗时,但通常仍然远小于深拷贝本身的开销。
兼容性方面,jQuery 3.x 已经放弃了 IE8 及更早版本,因此可以安全使用 Object.getPrototypeOf 和 Function.prototype.toString 等 ES5 方法。如果你仍需兼容旧环境,可以使用 jQuery 1.x,但需要接受其对 Object.create(null) 返回 false 的差异。在项目升级 jQuery 版本时,应回归测试所有依赖 isPlainObject 的代码,尤其是那些传入无原型对象或跨 iframe 对象的场景。
最后强调,尽管 isPlainObject 能精确识别纯对象,但并不意味着其它对象类型就应该被限制使用。数组、Map、Set、类实例等都有其适用场景。工具方法的目的是在需要“纯数据容器”语义时提供可靠判断,而不是鼓励将所有数据处理都塞进普通对象。合理选择数据结构,配合准确的类型检测,才能写出稳健的 JavaScript 代码。
jQuery.isPlainObject纯对象判断对象原型修改时间:2026-08-27 07:38:09