功能概述
能判断各种各样的数据类型是不是"空的"。不管你给它数组、对象、Map、Set,还是字符串,它都能准确告诉你是不是空的。而且实现方式很巧妙,针对不同类型的数据采用不同的判断策略。
前置学习
在深入理解 isEmpty 之前,建议先了解以下相关函数:
- isArrayLike:检测一个值是否类似数组,具有 length 属性且为非负整数
- isArray:检测值是否为 Array 类型
- isBuffer:检测值是否为 Buffer 类型(Node.js)
- isTypedArray:检测值是否为类型化数组(如 Int8Array、Float32Array 等)
- isArguments:检测值是否为函数的 arguments 对象
- getTag:获取值的内部 [[Class]] 标签,用于区分 Map、Set 等特殊对象类型
- isPrototype:检测对象是否为构造函数的原型对象
- baseKeys:获取对象自身的可枚举属性名数组的基础实现
这些辅助函数共同构成了 isEmpty 的判断体系,使它能够准确处理各种数据类型的空值检测。
源码实现
function isEmpty(value) {
if (value == null) {
return true;
}
if (
isArrayLike(value) &&
(isArray(value) ||
typeof value == "string" ||
typeof value.splice == "function" ||
isBuffer(value) ||
isTypedArray(value) ||
isArguments(value))
) {
return !value.length;
}
var tag = getTag(value);
if (tag == mapTag || tag == setTag) {
return !value.size;
}
if (isPrototype(value)) {
return !baseKeys(value).length;
}
for (var key in value) {
if (hasOwnProperty.call(value, key)) {
return false;
}
}
return true;
}
实现原理解析
整体思路概述
这个函数的实现思路非常清晰,就像走一个决策树:
- 先处理最简单的 null/undefined 情况
- 然后判断是不是数组类型的(包括数组、字符串、类数组等)
- 接着看看是不是 Map/Set 类型
- 再检查是不是原型对象
- 最后处理普通对象的情况
每一步都针对特定类型采用最合适的判空方式,下面我们来详细看看每种情况。
1. null/undefined 判断
if (value == null) {
return true;
}
这是最基础的判断,用双等号(==)来同时处理 null 和 undefined:
console.log(isEmpty(null)); // true
console.log(isEmpty(undefined)); // true
// 但其他假值不一定是空的
console.log(isEmpty(0)); // false
console.log(isEmpty(false)); // false
2. 数组类型判断
if (
isArrayLike(value) &&
(isArray(value) ||
typeof value == "string" ||
typeof value.splice == "function" ||
isBuffer(value) ||
isTypedArray(value) ||
isArguments(value))
) {
return !value.length;
}
这段代码处理所有有 length 属性的类型,包括:
// 数组
console.log(isEmpty([])); // true
console.log(isEmpty([1, 2])); // false
// 字符串
console.log(isEmpty("")); // true
console.log(isEmpty("hello")); // false
// 类数组对象
function foo() {
console.log(isEmpty(arguments)); // true
}
foo();
// 类型化数组
console.log(isEmpty(new Int8Array())); // true
console.log(isEmpty(new Int8Array(2))); // false
3. Map/Set 类型判断
var tag = getTag(value);
if (tag == mapTag || tag == setTag) {
return !value.size;
}
对于 Map 和 Set,我们看它们的 size 属性:
// Map
const map = new Map();
console.log(isEmpty(map)); // true
map.set("key", "value");
console.log(isEmpty(map)); // false
// Set
const set = new Set();
console.log(isEmpty(set)); // true
set.add(1);
console.log(isEmpty(set)); // false
4. 原型对象判断
if (isPrototype(value)) {
return !baseKeys(value).length;
}
处理原型对象的情况:
// 创建一个构造函数
function Person() {}
Person.prototype.name = "John";
// 检查原型对象
console.log(isEmpty(Person.prototype)); // false
// 空的原型对象
function Empty() {}
console.log(isEmpty(Empty.prototype)); // true
5. 普通对象判断
for (var key in value) {
if (hasOwnProperty.call(value, key)) {
return false;
}
}
return true;
最后处理普通对象,看看有没有自己的可枚举属性:
// 空对象
console.log(isEmpty({})); // true
// 有属性的对象
console.log(isEmpty({ name: "John" })); // false
// 只有不可枚举属性的对象
const obj = Object.defineProperty({}, "name", {
value: "John",
enumerable: false,
});
console.log(isEmpty(obj)); // true
总结
通过一系列的条件判断,优雅地处理了各种不同类型的空值判断。不同类型用不同的判空标准:length、size、自有属性等。而且代码结构清晰,判断逻辑层层递进。