功能概述
isPlainObject 函数是 Lodash 中用于判断一个值是否为纯粹对象(plain object)的工具函数。所谓纯粹对象,指的是通过对象字面量 {} 或 new Object() 创建的对象。这个函数在处理配置对象、深拷贝等场景中发挥着重要作用。
前置学习
在深入理解 isPlainObject 之前,建议先了解以下相关函数:
- isObjectLike:检查值是否为类对象类型,是 isPlainObject 的第一层过滤
- baseGetTag:获取值的内部 [[Class]] 标签,用于确认对象类型
- getPrototype:获取对象的原型,用于原型链检查
此外,对以下 JavaScript 概念的理解也会有所帮助:
- 原型链:理解 JavaScript 的原型继承机制
- Object.prototype.constructor:了解构造函数与实例之间的关系
- Function.prototype.toString:如何通过函数的字符串表示来识别其来源
- instanceof 操作符:它如何检查构造函数与实例之间的关系
源码实现
function isPlainObject(value) {
if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
return false;
}
var proto = getPrototype(value);
if (proto === null) {
return true;
}
var Ctor = hasOwnProperty.call(proto, "constructor") && proto.constructor;
return (
typeof Ctor == "function" &&
Ctor instanceof Ctor &&
funcToString.call(Ctor) === objectCtorString
);
}
实现原理解析
1. 基础类型检查
函数首先进行两个基础检查:
if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
return false;
}
- 通过
isObjectLike确保值是类对象(非 null 的对象) - 使用
baseGetTag获取对象的原始类型标签,必须是 '[object Object]'
2. 原型链检查
获取对象的原型后进行判断:
var proto = getPrototype(value);
if (proto === null) {
return true;
}
如果对象没有原型(proto 为 null),则认为是纯粹对象,比如以下创建对象方式
Object.create(null)Object.setPrototypeOf({}, null);
3. 构造函数验证
最后一步是验证对象的构造函数:
var Ctor = hasOwnProperty.call(proto, "constructor") && proto.constructor;
return (
typeof Ctor == "function" &&
Ctor instanceof Ctor &&
funcToString.call(Ctor) === objectCtorString
);
3.1 hasOwnProperty.call(proto, "constructor")
确保 proto 对象自身拥有 constructor 属性,避免因原型污染或手动修改导致的误判。
// 假设原型对象被修改,丢失了 constructor 属性
const obj = Object.create({ __proto__: null });
obj.constructor = null; // 手动删除 constructor 属性
// 若不检查 hasOwnProperty,直接通过 proto.constructor 获取会得到 undefined
// 导致后续类型判断失败
3.2 proto.constructor
获取原型链中对象的构造函数。
const obj = new (function Custom() {})();
const proto = Object.getPrototypeOf(obj); // 获取原型对象
// proto.constructor 指向 Custom 函数(自定义构造函数)
// 若 proto.constructor 为 undefined(如 Object.create(null)),则跳过后续检查
3.3 typeof Ctor == "function"
过滤掉非函数类型的构造器(如字符串、数字等),确保后续逻辑安全。
const obj = {};
// 将 constructor 属性设为字符串
Object.setPrototypeOf(obj, { constructor: "not a function" });
// Ctor 为 "not a function"(字符串类型),typeof Ctor 为 "string",条件不成立
3.4 Ctor instanceof Ctor
作用:确保构造函数未被篡改或污染,防止通过修改原型链绕过类型检查。 示例:
// 假设原型链被恶意修改
function Malicious() {}
Malicious.prototype = Object.create(Array.prototype); // 将原型指向 Array.prototype
const obj = new Malicious();
const proto = Object.getPrototypeOf(obj); // 获取原型对象
const Ctor = proto.constructor; // Ctor 指向 Malicious 函数
// Malicious 函数的原型被修改为 Array.prototype,此时 Ctor instanceof Ctor 为 false
3.5 funcToString.call(Ctor) === objectCtorString
区分 Object 构造函数与其他自定义构造函数(如 Array、Date 等),确保仅纯对象通过验证。
// 自定义构造函数
function Custom() {}
const obj = new Custom();
const proto = Object.getPrototypeOf(obj); // 获取原型对象
const Ctor = proto.constructor; // Ctor 指向 Custom 函数
// funcToString.call(Ctor) 返回 "[object Function]",与 objectCtorString("[object Object]")不匹配
这些条件共同确保了:
- 原型链未被篡改(条件 1、4、5)。
- 构造函数类型正确(条件 3、5)。
- 仅通过 {} 或 new Object() 创建的纯对象通过验证(条件 2、4、5)。
总结
isPlainObject 函数通过多层次的检查机制,准确识别纯粹对象:
- 首先确保值是对象类型且具有正确的类型标签
- 然后检查原型链,处理无原型对象的特殊情况
- 最后通过构造函数的严格验证,确保对象是通过 Object 构造函数或对象字面量创建