JavaScript类型判断完全指南:null、undefined与高级类型检测

引言

JavaScript 作为一门动态类型语言,类型判断是日常开发中不可或缺的操作。然而,nullundefined 的特殊行为,以及隐式类型转换的复杂性,常常让开发者感到困惑。本文将系统讲解 JavaScript 中的类型判断方法,从基础到高级,帮助你写出更健壮的代码。

基础概念

null vs undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
// undefined:表示变量已声明但未赋值
let a;
console.log(a); // undefined
console.log(typeof a); // "undefined"

// null:表示变量有值,但值为"空"
let b = null;
console.log(b); // null
console.log(typeof b); // "object" (历史遗留bug)

// 有趣的比较
console.log(null == undefined); // true(宽松相等)
console.log(null === undefined); // false(严格相等)

两者区别:

特性 undefined null
含义 未初始化 空值
typeof “undefined” “object”
转换为数字 NaN 0
使用场景 系统默认 主动赋空

基础类型判断

1. 判断 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 方法1:typeof(推荐)
let a;
if (typeof a === "undefined") {
console.log("a is undefined");
}

// 方法2:严格相等
if (a === undefined) {
console.log("a is undefined");
}

// 为什么不推荐 ==?
let b = null;
console.log(b == undefined); // true(意外!)
console.log(b === undefined); // false

最佳实践: 使用 typeof 判断 undefined,避免变量未声明时报错

1
2
3
4
5
6
7
8
9
// typeof 不会报错
if (typeof notDeclaredVar === "undefined") {
console.log("变量未声明");
}

// 直接比较会报错
if (notDeclaredVar === undefined) {
// ReferenceError: notDeclaredVar is not defined
}

2. 判断 null

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方法1:严格相等(推荐)
let a = null;
if (a === null) {
console.log("a is null");
}

// 方法2:typeof + 比较(兼容性方案)
if (typeof a === "object" && a === null) {
console.log("a is null");
}

// 常见错误:typeof 陷阱
console.log(typeof null); // "object"!不是 "null"

3. 同时判断 null 和 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 方法1:宽松相等(推荐)
let value;
if (value == null) {
// 同时匹配 null 和 undefined
console.log("value is null or undefined");
}

// 方法2:ES2020 空值合并运算符
let result = value ?? "default";
// value 为 null 或 undefined 时返回 "default"

// 方法3:逻辑或(注意:0 和 "" 也会触发)
let result2 = value || "default";

// 方法4:显式判断
if (value === null || value === undefined) {
console.log("value is null or undefined");
}

高级类型检测

1. 完整类型判断工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
const TypeChecker = {
// 基础类型判断
isUndefined(value) {
return typeof value === "undefined";
},

isNull(value) {
return value === null;
},

isNullOrUndefined(value) {
return value == null;
},

isString(value) {
return typeof value === "string";
},

isNumber(value) {
return typeof value === "number" && !isNaN(value);
},

isBoolean(value) {
return typeof value === "boolean";
},

isSymbol(value) {
return typeof value === "symbol";
},

isBigInt(value) {
return typeof value === "bigint";
},

isFunction(value) {
return typeof value === "function";
},

isObject(value) {
return typeof value === "object" && value !== null;
},

isArray(value) {
return Array.isArray(value);
},

isPlainObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
},

isDate(value) {
return value instanceof Date;
},

isRegExp(value) {
return value instanceof RegExp;
},

isError(value) {
return value instanceof Error;
},

isPromise(value) {
return value instanceof Promise ||
(this.isObject(value) && typeof value.then === "function");
},

// 特殊值判断
isNaN(value) {
return Number.isNaN(value);
},

isInfinity(value) {
return value === Infinity || value === -Infinity;
},

isFinite(value) {
return Number.isFinite(value);
},

isEmpty(value) {
if (this.isNullOrUndefined(value)) return true;
if (this.isString(value) || this.isArray(value)) {
return value.length === 0;
}
if (this.isPlainObject(value)) {
return Object.keys(value).length === 0;
}
return false;
}
};

// 使用示例
console.log(TypeChecker.isNullOrUndefined(null)); // true
console.log(TypeChecker.isPlainObject({})); // true
console.log(TypeChecker.isPlainObject([])); // false
console.log(TypeChecker.isEmpty("")); // true
console.log(TypeChecker.isEmpty({})); // true

2. Object.prototype.toString 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 最可靠的对象类型判断方法
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}

// 使用示例
console.log(getType(undefined)); // "Undefined"
console.log(getType(null)); // "Null"
console.log(getType(123)); // "Number"
console.log(getType("abc")); // "String"
console.log(getType(true)); // "Boolean"
console.log(getType([])); // "Array"
console.log(getType({})); // "Object"
console.log(getType(new Date())); // "Date"
console.log(getType(/abc/)); // "RegExp"
console.log(getType(new Map())); // "Map"
console.log(getType(new Set())); // "Set"
console.log(getType(function(){})); // "Function"

3. instanceof 运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 检查原型链
class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

// 局限性:跨 iframe/window 会失效
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;
console.log([] instanceof iframeArray); // false

// 解决方案:使用 Object.prototype.toString
console.log(Object.prototype.toString.call([]) === "[object Array]"); // true

实际应用场景

1. 安全的属性访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 深度对象属性访问
function getDeepValue(obj, path, defaultValue = undefined) {
if (TypeChecker.isNullOrUndefined(obj)) return defaultValue;

const keys = path.split('.');
let current = obj;

for (const key of keys) {
if (TypeChecker.isNullOrUndefined(current[key])) {
return defaultValue;
}
current = current[key];
}

return current;
}

// 使用
const user = {
profile: {
name: "John",
address: {
city: "Beijing"
}
}
};

console.log(getDeepValue(user, "profile.name")); // "John"
console.log(getDeepValue(user, "profile.address.city")); // "Beijing"
console.log(getDeepValue(user, "profile.phone", "N/A")); // "N/A"
console.log(getDeepValue(null, "profile.name", "N/A")); // "N/A"

2. 表单验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Validator {
static required(value) {
if (TypeChecker.isNullOrUndefined(value)) return false;
if (TypeChecker.isString(value) && value.trim() === "") return false;
if (TypeChecker.isArray(value) && value.length === 0) return false;
return true;
}

static isEmail(value) {
if (!TypeChecker.isString(value)) return false;
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

static isPhone(value) {
if (!TypeChecker.isString(value) && !TypeChecker.isNumber(value)) {
return false;
}
return /^1[3-9]\d{9}$/.test(String(value));
}

static isInteger(value) {
return TypeChecker.isNumber(value) && Number.isInteger(value);
}

static inRange(value, min, max) {
return TypeChecker.isNumber(value) && value >= min && value <= max;
}
}

// 使用
const formData = {
name: "John",
email: "john@example.com",
age: 25
};

console.log(Validator.required(formData.name)); // true
console.log(Validator.isEmail(formData.email)); // true
console.log(Validator.inRange(formData.age, 18, 60)); // true

3. 类型转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const TypeConverter = {
toString(value, defaultValue = "") {
if (TypeChecker.isNullOrUndefined(value)) return defaultValue;
return String(value);
},

toNumber(value, defaultValue = 0) {
if (TypeChecker.isNullOrUndefined(value)) return defaultValue;
const num = Number(value);
return isNaN(num) ? defaultValue : num;
},

toBoolean(value) {
// falsy 值返回 false
if (!value) return false;
// 字符串 "false"、"0" 等返回 false
if (TypeChecker.isString(value)) {
return !["false", "0", "", "null", "undefined"].includes(value.toLowerCase());
}
return true;
},

toArray(value) {
if (TypeChecker.isNullOrUndefined(value)) return [];
if (TypeChecker.isArray(value)) return value;
return [value];
},

toDate(value) {
if (TypeChecker.isDate(value)) return value;
if (TypeChecker.isNumber(value) || TypeChecker.isString(value)) {
const date = new Date(value);
return isNaN(date.getTime()) ? null : date;
}
return null;
}
};

// 使用
console.log(TypeConverter.toNumber("123")); // 123
console.log(TypeConverter.toNumber("abc", 0)); // 0
console.log(TypeConverter.toBoolean("false")); // false
console.log(TypeConverter.toArray("item")); // ["item"]

常见陷阱与解决方案

1. typeof null === “object”

1
2
3
4
5
6
7
8
9
10
11
// 陷阱
console.log(typeof null); // "object"

// 解决方案
function isNull(value) {
return value === null;
}

function isObject(value) {
return typeof value === "object" && value !== null;
}

2. NaN 的判断

1
2
3
4
5
6
7
// 陷阱:NaN 不等于自身
console.log(NaN === NaN); // false

// 解决方案
console.log(Number.isNaN(NaN)); // true
console.log(isNaN("abc")); // true(先转换再判断)
console.log(Number.isNaN("abc")); // false(不转换)

3. 数组的类型判断

1
2
3
4
5
6
// typeof 数组返回 "object"
console.log(typeof []); // "object"

// 正确方法
console.log(Array.isArray([])); // true
console.log(Object.prototype.toString.call([])); // "[object Array]"

4. 构造函数判断的局限

1
2
3
4
5
6
7
8
9
10
// 跨窗口问题
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const IframeArray = iframe.contentWindow.Array;

const arr = new IframeArray();

console.log(arr instanceof Array); // false!
console.log(Array.isArray(arr)); // true
console.log(Object.prototype.toString.call(arr)); // "[object Array]"

最佳实践总结

1. 优先使用严格相等

1
2
3
4
5
// ✅ 推荐
if (value === null || value === undefined) { }

// ❌ 避免
if (value == null) { } // 虽然可以,但不明确

2. 使用专用方法判断对象类型

1
2
3
4
5
6
7
8
// ✅ 推荐
Array.isArray(value);
Number.isNaN(value);
Object.prototype.toString.call(value);

// ❌ 避免
value instanceof Array; // 跨窗口问题
isNaN(value); // 隐式转换问题

3. 防御性编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function safeExecute(fn, defaultValue = undefined) {
return function(...args) {
try {
if (!TypeChecker.isFunction(fn)) {
return defaultValue;
}
return fn.apply(this, args);
} catch (error) {
console.error("Execution error:", error);
return defaultValue;
}
};
}

// 使用
const safeParse = safeExecute(JSON.parse, {});
console.log(safeParse('{"a":1}')); // {a: 1}
console.log(safeParse('invalid')); // {}

总结

JavaScript 类型判断的关键点:

  1. undefined 判断:使用 typeof value === "undefined"
  2. null 判断:使用 value === null
  3. null 和 undefined 同时判断:使用 value == null 或显式判断
  4. 对象类型判断:使用 Object.prototype.toString
  5. 数组判断:使用 Array.isArray
  6. NaN 判断:使用 Number.isNaN

掌握这些类型判断方法,可以写出更健壮、更可靠的 JavaScript 代码。