引言
JavaScript作为一种灵活的编程语言,其类型系统是开发者必须深入理解的基础知识。本文将详细剖析JavaScript的数据类型体系,特别关注简单类型与复杂类型的区别,以及类型判断的各种方法和陷阱。
JavaScript的类型体系
JavaScript共有七种数据类型,可分为两大类:
简单数据类型(Primitive Types)
简单数据类型包括:
- string(字符串)
- boolean(布尔值)
- null(空值)
- undefined(未定义)
- symbol(符号,ES6新增)
- numeric(数值类型,包括number和bigint)
其中numeric又可细分为:
- number:常规数字
- bigint:大整数(ES2020引入)
复杂数据类型(Reference Types)
复杂数据类型只有一种:
- object(对象)
但object包含多种形式:普通对象、数组、函数、日期、正则表达式等。
内存模型:简单类型vs复杂类型
简单数据类型
// 简单数据类型存储在栈内存中
let a = 10;
let b = a; // 值拷贝
b = 20; // 修改b不影响a
console.log(a); // 10
简单数据类型具有以下特点:
- 存储在栈内存中
- 拷贝式赋值(值传递)
- 固定大小,访问迅速
复杂数据类型
// 复杂数据类型的引用存储在栈内存,数据存储在堆内存
let obj1 = {name: '张三'};
let obj2 = obj1; // 引用传递
obj2.name = '李四'; // 修改obj2同时影响obj1
console.log(obj1.name); // 李四
复杂数据类型具有以下特点:
- 引用(地址)存储在栈内存中
- 实际数据存储在堆内存中
- 引用式赋值(地址传递)
- 大小可变
类型判断
typeof运算符
从提供的代码中,我们可以看到typeof是判断类型的基础工具:
function add(a,b){
// 参数的校验
if(typeof a !== 'number' || typeof b !== 'number' || isNaN(a) || isNaN(b)){
throw new TypeError('参数必须是数字');
}
return a+b;
}
typeof运算符返回类型的字符串表示:
| 值 | typeof返回值 |
|---|---|
| 'hello' | 'string' |
| 123 | 'number' |
| true | 'boolean' |
| undefined | 'undefined' |
| Symbol() | 'symbol' |
| 123n | 'bigint' |
| {} | 'object' |
| null | 'object'(这是一个历史遗留bug) |
| function(){} | 'function' |
typeof的局限性
- 无法识别null:
typeof null返回'object'而非'null' - 无法区分具体的对象类型:无法区分Array、Date等具体对象类型
增强的类型判断
// 对null的判断
function isNull(value) {
return value === null;
}
// 使用instanceof判断对象类型
function isArray(value) {
return value instanceof Array;
}
// 更可靠的数组判断
function isArrayReliable(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
Numeric类型详解
JavaScript的numeric类型包括两种子类型:
Number类型
Number类型包括整数和浮点数,可表示的范围是±(2^53-1)。
let integer = 100;
let float = 3.14;
let scientific = 1e6; // 科学记数法:1000000
特殊的Number值:
Infinity:无穷大-Infinity:负无穷大NaN:非数值(Not a Number)
BigInt类型
为了解决Number类型表示大整数的限制,ES2020引入了BigInt类型:
const bigNumber = 9007199254740991n; // 末尾添加n表示BigInt
const result = bigNumber + 1n; // BigInt只能与BigInt运算
BigInt特点:
- 可以表示任意精度的整数
- 不能与Number类型直接运算
- 不能使用Math对象方法
NaN的本质解析
从提供的代码中,我们可以看到NaN的多种产生方式:
console.log(0/0); // NaN
console.log(Math.sqrt(-1)); // NaN
console.log(parseInt("123"), parseInt("a123")); // 123, NaN
console.log(Number(undefined)); // NaN
NaN的特性
- NaN是Number类型:
typeof NaN返回'number' - NaN不等于任何值,包括它自身:
NaN === NaN返回false - 多种方式可产生NaN:
- 非法数学运算(如0/0)
- 无法转换为数字的字符串转换
- 对undefined进行数值转换
- 检测NaN:需要使用
isNaN()函数或ES6的Number.isNaN()
console.log(NaN === NaN); // false
console.log(isNaN(NaN), isNaN(0/0)); // true true
// ES6提供的更精确的方法
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('NaN')); // false,不进行类型转换
isNaN与Number.isNaN的区别
isNaN()会先尝试将参数转换为数字,而Number.isNaN()不会进行转换:
console.log(isNaN('abc')); // true,'abc'转换为数字失败,结果是NaN
console.log(Number.isNaN('abc')); // false,'abc'不是NaN
实际应用:健壮的类型检查
基于以上知识,我们可以编写更健壮的类型检查函数:
function add(a, b) {
// 参数的校验
if(typeof a !== 'number' || typeof b !== 'number' || isNaN(a) || isNaN(b)) {
throw new TypeError('参数必须是数字');
}
return a + b;
}
// 测试
try {
console.log(add(1, 2)); // 3
console.log(add(1, 'a')); // 抛出TypeError
console.log(add(1, NaN)); // 抛出TypeError
} catch(e) {
console.error(e.message);
}
总结
JavaScript的类型系统虽然简单,但也有其复杂之处和需要注意的细节:
- 简单类型:存储在栈中,值传递,包括string、boolean、null、undefined、symbol和numeric(number和bigint)
- 复杂类型:引用存储在栈中,数据存储在堆中,引用传递
- 类型判断:typeof适合大多数简单类型判断,但对null和具体对象类型的判断需要其他方法
- NaN:虽然名为"非数字",但它是number类型的一个特殊值,具有独特的比较规则
深入理解JavaScript的类型系统,不仅能帮助我们编写更健壮的代码,还能避免许多常见的类型相关陷阱。