1.26 - 数据类型、类型判断、类型转换
JavaScript 数据类型完全指南
目录
数据类型概述
JavaScript 的 8 种数据类型
基本类型(7种):
String(字符串)Number(数字)Boolean(布尔)Undefined(未定义)Null(空)Symbol(符号,ES6)BigInt(大整数,ES2020)
引用类型(1种):
Object(对象)- 普通对象
{} - 数组
[] - 函数
function - 日期
Date - 正则
RegExp - 等等…
- 普通对象
存储方式的本质区别
// 基本类型 - 栈内存存储
let a = 10;
let b = a; // 复制值
b = 20;
console.log(a); // 10 - a 不受影响
// 引用类型 - 堆内存存储,栈存储引用地址
let obj1 = { name: 'Tom' };
let obj2 = obj1; // 复制引用地址
obj2.name = 'Jerry';
console.log(obj1.name); // 'Jerry' - obj1 也被修改了
关键理解点:
- 基本类型按值存储和访问
- 引用类型按引用存储和访问
- 基本类型赋值是深拷贝
- 引用类型赋值是浅拷贝
基本数据类型详解
1. String(字符串)
// 创建方式
let str1 = 'hello'; // 字面量(推荐)
let str2 = "world"; // 双引号
let str3 = `template`; // 模板字符串(ES6)
let str4 = new String('obj'); // 对象形式(不推荐)
// 特点
console.log(typeof str1); // 'string'
console.log(typeof str4); // 'object'
console.log(str1 === str4); // false - 类型不同
// 不可变性
let s = 'hello';
s[0] = 'H'; // 无效操作
console.log(s); // 'hello' - 字符串不可变
// 常用方法
'hello'.length; // 5
'hello'.charAt(0); // 'h'
'hello'.substring(1, 3); // 'el'
'hello'.toUpperCase(); // 'HELLO'
2. Number(数字)
// 特殊值
console.log(0.1 + 0.2 === 0.3); // false - 浮点精度问题
console.log(Infinity); // 正无穷
console.log(-Infinity); // 负无穷
console.log(NaN); // Not a Number
console.log(typeof NaN); // 'number' ⚠️
// NaN 的特性
console.log(NaN === NaN); // false - NaN 不等于任何值,包括自己
console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true(更严格,推荐)
// 安全整数范围
console.log(Number.MAX_SAFE_INTEGER); // 2^53 - 1 = 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -(2^53 - 1)
// 精度问题解决方案
function add(a, b) {
return (a * 100 + b * 100) / 100; // 转整数计算
}
console.log(add(0.1, 0.2)); // 0.3
3. Boolean(布尔)
let bool1 = true;
let bool2 = false;
// Falsy 值(转换为 false 的值)- 重要!
Boolean(false); // false
Boolean(0); // false
Boolean(-0); // false
Boolean(''); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
// 其他所有值都是 Truthy(转换为 true)
Boolean('0'); // true ⚠️
Boolean('false'); // true ⚠️
Boolean([]); // true ⚠️
Boolean({}); // true ⚠️
Boolean(function(){}); // true
4. Undefined
// 出现场景
let a;
console.log(a); // undefined - 声明未赋值
function fn(x) {
console.log(x); // undefined - 参数未传递
}
fn();
let obj = {};
console.log(obj.name); // undefined - 访问不存在的属性
function test() {}
console.log(test()); // undefined - 函数没有返回值
// 注意
console.log(typeof undefined); // 'undefined'
console.log(undefined == null); // true ⚠️
console.log(undefined === null); // false
5. Null
// 语义:表示"空对象",有意的空值
let obj = null; // 明确表示这个变量将来要存对象,现在为空
// 特殊的 typeof bug
console.log(typeof null); // 'object' ⚠️ 历史遗留问题
// 判断 null
let val = null;
console.log(val === null); // true - 推荐方式
console.log(!val && typeof val === 'object'); // true - 完整判断
// null vs undefined
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
6. Symbol(ES6)
// 创建唯一值
let s1 = Symbol();
let s2 = Symbol();
console.log(s1 === s2); // false - 每个 Symbol 都是唯一的
let s3 = Symbol('desc');
let s4 = Symbol('desc');
console.log(s3 === s4); // false - 描述相同也不相等
// 使用场景1:对象的唯一属性键
const NAME = Symbol('name');
const obj = {
[NAME]: 'Tom',
age: 18
};
console.log(obj[NAME]); // 'Tom'
console.log(Object.keys(obj)); // ['age'] - Symbol 属性不可枚举
// 使用场景2:定义常量
const COLOR_RED = Symbol('red');
const COLOR_GREEN = Symbol('green');
// Symbol.for() - 全局注册
let s5 = Symbol.for('app.id');
let s6 = Symbol.for('app.id');
console.log(s5 === s6); // true - 相同 key 返回同一个 Symbol
7. BigInt(ES2020)
// 创建大整数
let big1 = 1234567890123456789012345678901234567890n; // 后缀 n
let big2 = BigInt('1234567890123456789012345678901234567890');
let big3 = BigInt(123);
// 运算
console.log(10n + 20n); // 30n
console.log(10n * 2n); // 20n
// 注意事项
console.log(10n === 10); // false - 类型不同
console.log(10n == 10); // true
// console.log(10n + 10); // TypeError - 不能混合运算
console.log(10n + BigInt(10)); // 20n - 需要显式转换
// 使用场景
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
console.log(maxSafe + 1n); // 9007199254740992n
console.log(maxSafe + 2n); // 9007199254740993n - 超过 Number 范围
引用数据类型详解
Object(对象)
// 创建方式
let obj1 = {}; // 字面量(推荐)
let obj2 = new Object(); // 构造函数
let obj3 = Object.create(null); // 原型创建
// 常见对象类型
let arr = []; // Array
let fn = function(){}; // Function
let date = new Date(); // Date
let reg = /test/; // RegExp
let err = new Error(); // Error
let map = new Map(); // Map(ES6)
let set = new Set(); // Set(ES6)
// 所有这些的 typeof 都是 'object'(除了 function)
console.log(typeof arr); // 'object'
console.log(typeof fn); // 'function' ⚠️ 特例
console.log(typeof date); // 'object'
包装对象
// 基本类型的包装对象
let str = 'hello';
console.log(str.toUpperCase()); // 'HELLO'
// 背后发生了:
// 1. 临时创建 String 对象:new String('hello')
// 2. 调用方法:对象.toUpperCase()
// 3. 销毁临时对象
// 显式创建包装对象(不推荐)
let strObj = new String('hello');
console.log(typeof str); // 'string'
console.log(typeof strObj); // 'object'
console.log(str == strObj); // true
console.log(str === strObj); // false
// 自动装箱和拆箱
let num = 123;
let numObj = new Number(123);
console.log(num + numObj); // 246 - 自动拆箱
类型判断方法
1. typeof 操作符
// 基本类型
console.log(typeof 'hello'); // 'string'
console.log(typeof 123); // 'number'
console.log(typeof true); // 'boolean'
console.log(typeof undefined); // 'undefined'
console.log(typeof Symbol()); // 'symbol'
console.log(typeof 123n); // 'bigint'
// 引用类型
console.log(typeof null); // 'object' ⚠️ bug
console.log(typeof {}); // 'object'
console.log(typeof []); // 'object' ⚠️
console.log(typeof function(){}); // 'function' ✓
// typeof 的局限性
console.log(typeof new Date()); // 'object'
console.log(typeof /test/); // 'object'
console.log(typeof new String()); // 'object'
// 安全检查未声明的变量
// console.log(undeclaredVar); // ReferenceError
console.log(typeof undeclaredVar); // 'undefined' - 不报错
typeof 使用总结:
- ✅ 适合判断基本类型(除了 null)
- ✅ 可以判断 function
- ❌ 不能区分 null、对象、数组
- ❌ 不能细分对象类型
2. instanceof 操作符
// 原理:检查构造函数的 prototype 是否在对象的原型链上
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true
// 继承关系
console.log([] instanceof Object); // true - Array 继承自 Object
console.log([] instanceof Array); // true
// 局限性
console.log('hello' instanceof String); // false - 基本类型
console.log(new String('hello') instanceof String); // true
console.log(null instanceof Object); // false
console.log(undefined instanceof Object); // false
// 跨 iframe 或 window 问题
// 不同上下文的构造函数不是同一个引用
// [] instanceof window.Array // true
// [] instanceof iframe.Array // false
// 手动实现 instanceof
function myInstanceof(obj, constructor) {
if (obj === null || typeof obj !== 'object') {
return false;
}
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof('hello', String)); // false
instanceof 使用总结:
- ✅ 适合判断对象类型
- ✅ 可以判断继承关系
- ❌ 不能判断基本类型
- ❌ 跨上下文会失效
3. Object.prototype.toString.call()
// 最准确的类型判断方法
const toString = Object.prototype.toString;
// 基本类型
console.log(toString.call('hello')); // '[object String]'
console.log(toString.call(123)); // '[object Number]'
console.log(toString.call(true)); // '[object Boolean]'
console.log(toString.call(undefined)); // '[object Undefined]'
console.log(toString.call(null)); // '[object Null]'
console.log(toString.call(Symbol())); // '[object Symbol]'
console.log(toString.call(123n)); // '[object BigInt]'
// 引用类型
console.log(toString.call({})); // '[object Object]'
console.log(toString.call([])); // '[object Array]'
console.log(toString.call(function(){})); // '[object Function]'
console.log(toString.call(new Date())); // '[object Date]'
console.log(toString.call(/test/)); // '[object RegExp]'
console.log(toString.call(new Error())); // '[object Error]'
console.log(toString.call(Math)); // '[object Math]'
console.log(toString.call(JSON)); // '[object JSON]'
// 封装通用类型判断函数
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}
console.log(getType(123)); // 'number'
console.log(getType([])); // 'array'
console.log(getType(new Date())); // 'date'
// 创建类型判断函数
function isType(type) {
return function(value) {
return getType(value) === type.toLowerCase();
};
}
const isArray = isType('Array');
const isObject = isType('Object');
const isFunction = isType('Function');
console.log(isArray([])); // true
console.log(isObject({})); // true
console.log(isFunction(() => {})); // true
Object.prototype.toString 使用总结:
- ✅ 最准确、最全面
- ✅ 能判断所有类型
- ✅ 不受跨上下文影响
- ⚠️ 需要记住调用方式
4. 其他判断方法
// Array.isArray() - ES5
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray('array')); // false
// Number.isNaN() - ES6(比 isNaN 更严格)
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('hello')); // false
console.log(isNaN('hello')); // true ⚠️ 会先转换
// Number.isFinite()
console.log(Number.isFinite(123)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite('123')); // false
// Number.isInteger()
console.log(Number.isInteger(123)); // true
console.log(Number.isInteger(123.45)); // false
console.log(Number.isInteger('123')); // false
// constructor 属性(不推荐,可被修改)
console.log([].constructor === Array); // true
console.log({}.constructor === Object); // true
类型判断方法对比表
| 方法 | null | undefined | string | number | boolean | symbol | bigint | object | array | function |
|---|---|---|---|---|---|---|---|---|---|---|
| typeof | ❌ object | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ object | ✅ |
| instanceof | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
| toString.call | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Array.isArray | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
类型转换机制
1. 显式转换
转换为字符串
// String()
String(123); // '123'
String(true); // 'true'
String(null); // 'null'
String(undefined); // 'undefined'
String([1, 2, 3]); // '1,2,3'
String({name: 'Tom'}); // '[object Object]'
// toString()
(123).toString(); // '123'
true.toString(); // 'true'
[1, 2, 3].toString(); // '1,2,3'
// null.toString(); // TypeError
// undefined.toString(); // TypeError
// 模板字符串
`${123}`; // '123'
`${true}`; // 'true'
// + 空字符串
123 + ''; // '123'
转换为数字
// Number()
Number('123'); // 123
Number('123.45'); // 123.45
Number(''); // 0 ⚠️
Number(' '); // 0 ⚠️
Number('123abc'); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0 ⚠️
Number(undefined); // NaN
Number([]); // 0 ⚠️
Number([1]); // 1
Number([1, 2]); // NaN
Number({}); // NaN
// parseInt() / parseFloat()
parseInt('123'); // 123
parseInt('123.45'); // 123
parseInt('123abc'); // 123 ⚠️ 转换到非数字为止
parseInt('abc123'); // NaN
parseInt('0x10'); // 16 - 十六进制
parseInt('10', 2); // 2 - 二进制
parseFloat('123.45'); // 123.45
parseFloat('123.45.67'); // 123.45
// + 运算符
+'123'; // 123
+true; // 1
+[]; // 0
转换为布尔值
// Boolean() 或 !!
Boolean(0); // false
Boolean(''); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
Boolean(false); // false
Boolean(1); // true
Boolean('0'); // true ⚠️
Boolean(' '); // true
Boolean([]); // true ⚠️
Boolean({}); // true ⚠️
// !! 双重取反(常用)
!!0; // false
!!'hello'; // true
2. 隐式转换
算术运算符
// + 运算符(特殊:既做加法又做字符串拼接)
1 + 2; // 3
'1' + 2; // '12' - 有字符串则拼接
1 + '2'; // '12'
1 + 2 + '3'; // '33' - 从左到右
'1' + 2 + 3; // '123'
true + 1; // 2
false + 1; // 1
null + 1; // 1
undefined + 1; // NaN
[] + 1; // '1' ⚠️
{} + 1; // 1 ⚠️ {} 被解析为代码块
({}) + 1; // '[object Object]1'
[] + []; // '' ⚠️
[] + {}; // '[object Object]'
{} + {}; // '[object Object][object Object]' or NaN
// - * / % 运算符(转数字)
'3' - 1; // 2
'3' * '2'; // 6
'6' / '2'; // 3
'5' % 2; // 1
'3' - 'a'; // NaN
true - 1; // 0
null - 1; // -1
undefined - 1; // NaN
'5' - true; // 4
比较运算符
// == 相等(会进行类型转换)
1 == '1'; // true
0 == ''; // true
0 == false; // true
null == undefined; // true ⚠️
null == 0; // false ⚠️
[] == 0; // true ⚠️
[] == ''; // true ⚠️
[] == false; // true ⚠️
[1] == 1; // true
// === 全等(不进行类型转换)
1 === '1'; // false
0 === false; // false
null === undefined; // false
// < > <= >= (转数字比较)
'2' > 1; // true
'2' > '12'; // true ⚠️ 字符串按字典序
'abc' > 'aac'; // true
true > false; // true
null > 0; // false
null >= 0; // true ⚠️
if 语句和逻辑运算符
// if 语句(转布尔值)
if (0) {/* 不执行 */}
if ('') {/* 不执行 */}
if (null) {/* 不执行 */}
if (undefined) {/* 不执行 */}
if (NaN) {/* 不执行 */}
if ([]) {/* 执行 */}
if ({}) {/* 执行 */}
// && 与运算(返回第一个假值或最后一个真值)
console.log(0 && 1); // 0
console.log(1 && 2); // 2
console.log(1 && 2 && 3); // 3
console.log('' && 'hello'); // ''
console.log('hello' && ''); // ''
// || 或运算(返回第一个真值或最后一个假值)
console.log(0 || 1); // 1
console.log(1 || 2); // 1
console.log('' || 'hello'); // 'hello'
console.log(null || undefined); // undefined
// ?? 空值合并运算符(ES2020)
console.log(0 ?? 1); // 0 - 只判断 null/undefined
console.log(null ?? 1); // 1
console.log(undefined ?? 1); // 1
console.log('' ?? 'default'); // ''
3. 对象转换规则
// 对象转换顺序:Symbol.toPrimitive > valueOf > toString
const obj = {
valueOf() {
console.log('valueOf');
return 1;
},
toString() {
console.log('toString');
return '2';
}
};
console.log(obj + 1); // valueOf -> 2
console.log(String(obj)); // toString -> '2'
// 自定义转换
const obj2 = {
[Symbol.toPrimitive](hint) {
console.log('hint:', hint);
if (hint === 'number') return 10;
if (hint === 'string') return 'hello';
return true;
}
};
console.log(obj2 + 1); // hint: default -> 2
console.log(String(obj2)); // hint: string -> 'hello'
console.log(+obj2); // hint: number -> 10
// 数组转换
const arr = [1, 2, 3];
console.log(String(arr)); // '1,2,3' - toString
console.log(Number(arr)); // NaN - valueOf 返回自身,toString 再转
console.log(arr.valueOf()); // [1, 2, 3]
console.log(arr.toString()); // '1,2,3'
// 日期转换
const date = new Date();
console.log(date.valueOf()); // 时间戳
console.log(date.toString()); // 日期字符串
console.log(date + 0); // 字符串拼接
console.log(+date); // 时间戳
类型转换规则总结
转字符串规则
- 基本类型:直接转换
- 对象:调用
toString()方法
转数字规则
Number('')→0Number(' ')→0Number(true)→1Number(false)→0Number(null)→0Number(undefined)→NaNNumber([])→0Number([n])→nNumber([n, m, ...])→NaN- 对象:先
valueOf(),再toString(),最后转数字
转布尔规则
- 7个假值:
false,0,-0,'',null,undefined,NaN - 其他都是真值(包括
[],{},'0','false')
面试高频考点
1. typeof null 为什么是 ‘object’?
console.log(typeof null); // 'object'
// 原因:这是 JavaScript 的历史遗留 bug
// 在 JS 最初的实现中,值用 32 位存储:
// - 对象标签(前3位):000
// - null 的所有位都是 0
// 因此 null 被误判为对象类型
2. 为什么 0.1 + 0.2 !== 0.3?
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
// 原因:浮点数精度问题(IEEE 754 标准)
// 解决方案:
function add(a, b) {
const precision = 10;
return (a * precision + b * precision) / precision;
}
// 或使用 Number.EPSILON
function equal(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(equal(0.1 + 0.2, 0.3)); // true
3. [] == ![] 为什么是 true?
console.log([] == ![]); // true
// 解析:
// 1. ![] 先执行,[] 是真值,![] = false
// 2. [] == false
// 3. ToPrimitive([]) → '' (调用 toString)
// 4. '' == false
// 5. 0 == 0
// 6. true
4. null 和 undefined 的区别
// undefined:未定义
let a; // 声明未赋值
console.log(a); // undefined
// null:空对象
let b = null; // 明确赋值为空
// 区别:
console.log(typeof null); // 'object'
console.log(typeof undefined); // 'undefined'
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(null == undefined); // true
console.log(null === undefined); // false
// 使用场景:
// undefined:变量未初始化、函数无返回值、对象属性不存在
// null:主动释放对象引用、API 返回空值
5. 如何判断一个值是 NaN?
// 方法1:Number.isNaN()(推荐)
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('hello')); // false
// 方法2:自身不等(NaN 是唯一不等于自身的值)
function isNaNValue(value) {
return value !== value;
}
console.log(isNaNValue(NaN)); // true
// 方法3:Object.prototype.toString
console.log(Object.prototype.toString.call(NaN)); // '[object Number]'
// 不推荐:isNaN()(会先转换类型)
console.log(isNaN(NaN)); // true
console.log(isNaN('hello')); // true ⚠️
console.log(isNaN(undefined)); // true ⚠️
6. 如何准确判断数组?
const arr = [];
// 方法1:Array.isArray()(推荐)
console.log(Array.isArray(arr)); // true
// 方法2:Object.prototype.toString
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true
// 方法3:instanceof(有跨窗口问题)
console.log(arr instanceof Array); // true
// 方法4:constructor(可被修改)
console.log(arr.constructor === Array); // true
7. == 和 === 的区别
// == 会进行类型转换
console.log(1 == '1'); // true
console.log(0 == false); // true
console.log(null == undefined); // true
// === 严格相等,不转换类型
console.log(1 === '1'); // false
console.log(0 === false); // false
console.log(null === undefined); // false
// 特殊情况
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false - 更严格
// 建议:始终使用 ===,避免隐式转换
8. Symbol 的应用场景
// 1. 唯一的对象属性键
const id = Symbol('id');
const user = {
name: 'Tom',
[id]: 123
};
console.log(user[id]); // 123
console.log(Object.keys(user)); // ['name'] - Symbol 不可枚举
// 2. 定义类的私有属性
const _counter = Symbol('counter');
class Counter {
constructor() {
this[_counter] = 0;
}
increment() {
this[_counter]++;
}
get value() {
return this[_counter];
}
}
// 3. 防止属性名冲突
const library1 = {
[Symbol.for('id')]: 'lib1-id'
};
const library2 = {
[Symbol.for('id')]: 'lib2-id'
};
// 4. 自定义对象的迭代行为
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return {
current: this.from,
last: this.to,
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
console.log([...range]); // [1, 2, 3, 4, 5]
实战技巧
1. 类型安全的工具函数
// 通用类型判断
const typeOf = (value) => {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
};
// 创建类型检查器
const is = {
string: (v) => typeof v === 'string',
number: (v) => typeof v === 'number' && !isNaN(v),
boolean: (v) => typeof v === 'boolean',
undefined: (v) => v === undefined,
null: (v) => v === null,
symbol: (v) => typeof v === 'symbol',
bigint: (v) => typeof v === 'bigint',
function: (v) => typeof v === 'function',
array: (v) => Array.isArray(v),
object: (v) => typeOf(v) === 'object',
plainObject: (v) => {
if (typeOf(v) !== 'object') return false;
const proto = Object.getPrototypeOf(v);
return proto === null || proto === Object.prototype;
},
date: (v) => v instanceof Date,
regexp: (v) => v instanceof RegExp,
error: (v) => v instanceof Error,
promise: (v) => v instanceof Promise,
empty: (v) => {
if (v === null || v === undefined) return true;
if (is.array(v) || is.string(v)) return v.length === 0;
if (is.object(v)) return Object.keys(v).length === 0;
return false;
}
};
// 使用示例
console.log(is.string('hello')); // true
console.log(is.array([])); // true
console.log(is.plainObject({})); // true
console.log(is.empty([])); // true
2. 安全的类型转换
// 转数字(带默认值)
function toNumber(value, defaultValue = 0) {
const num = Number(value);
return isNaN(num) ? defaultValue : num;
}
// 转字符串(处理 null/undefined)
function toString(value, defaultValue = '') {
if (value === null || value === undefined) {
return defaultValue;
}
return String(value);
}
// 转布尔(明确语义)
function toBoolean(value) {
return Boolean(value);
}
// 转数组
function toArray(value) {
if (Array.isArray(value)) return value;
if (value === null || value === undefined) return [];
return [value];
}
// 使用示例
console.log(toNumber('123')); // 123
console.log(toNumber('abc', -1)); // -1
console.log(toString(null, 'N/A')); // 'N/A'
console.log(toArray(1)); // [1]
console.log(toArray([1, 2])); // [1, 2]
3. 对象深拷贝(处理各种类型)
function deepClone(value, hash = new WeakMap()) {
// 基本类型直接返回
if (value === null || typeof value !== 'object') {
return value;
}
// 处理循环引用
if (hash.has(value)) {
return hash.get(value);
}
// Date
if (value instanceof Date) {
return new Date(value);
}
// RegExp
if (value instanceof RegExp) {
return new RegExp(value);
}
// Function
if (typeof value === 'function') {
return value;
}
// Array
if (Array.isArray(value)) {
const result = [];
hash.set(value, result);
value.forEach((item, index) => {
result[index] = deepClone(item, hash);
});
return result;
}
// Object
const result = {};
hash.set(value, result);
Object.keys(value).forEach(key => {
result[key] = deepClone(value[key], hash);
});
// Symbol 属性
Object.getOwnPropertySymbols(value).forEach(sym => {
result[sym] = deepClone(value[sym], hash);
});
return result;
}
// 测试
const obj = {
num: 123,
str: 'hello',
bool: true,
date: new Date(),
reg: /test/g,
arr: [1, 2, { nested: true }],
fn: () => console.log('hi')
};
obj.self = obj; // 循环引用
const cloned = deepClone(obj);
console.log(cloned);
console.log(cloned.self === cloned); // true
4. 防止类型错误的最佳实践
// 1. 使用严格相等
if (value === null) {}
if (value === undefined) {}
// 2. 显式类型转换
const num = Number(input);
const str = String(input);
const bool = Boolean(input);
// 3. 参数校验
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments must be numbers');
}
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
// 4. 使用 TypeScript 或 JSDoc
/**
* @param {string} name
* @param {number} age
* @returns {Object}
*/
function createUser(name, age) {
return { name, age };
}
// 5. 避免隐式转换陷阱
// ❌ 不好
if (value == null) {} // 同时匹配 null 和 undefined
// ✅ 好
if (value === null || value === undefined) {}
// ❌ 不好
const result = value || defaultValue; // 0, '', false 也会用默认值
// ✅ 好
const result = value ?? defaultValue; // 只有 null/undefined 用默认值
// 6. 数组/对象检查
// ❌ 不好
if (arr && arr.length) {}
// ✅ 好
if (Array.isArray(arr) && arr.length > 0) {}
5. 常见类型转换陷阱
// 陷阱1:字符串拼接
console.log(1 + 2 + '3'); // '33'
console.log('1' + 2 + 3); // '123'
// 陷阱2:布尔值转换
console.log(Boolean('0')); // true ⚠️
console.log(Boolean('false')); // true ⚠️
console.log(Boolean([])); // true ⚠️
console.log(Boolean({})); // true ⚠️
// 陷阱3:数组转换
console.log([] + []); // ''
console.log([] + {}); // '[object Object]'
console.log({} + []); // '[object Object]'
// 陷阱4:null 的比较
console.log(null == 0); // false
console.log(null >= 0); // true ⚠️
console.log(null == undefined); // true
// 陷阱5:NaN
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
// 陷阱6:对象键
const obj = {};
obj[{id: 1}] = 'value';
console.log(obj); // { '[object Object]': 'value' }
console.log(obj[{id: 2}]); // 'value' ⚠️ 键都转为字符串
总结与记忆口诀
类型判断口诀
typeof 基本快,null object 要记牢
instanceof 看原型,基本类型全是假
toString.call 最准确,万能判断不出错
Array.isArray 判数组,ES6 方法最靠谱
类型转换口诀
转字符串很简单,String 函数或加空串
转数字有门道,Number parseInt 分场合
转布尔记七假,其余全部都是真
对象转换看 Symbol,valueOf toString 有顺序
Falsy 值记忆(7个假值)
false、0、-0、''、null、undefined、NaN
学习建议
- 理解原理:不要死记硬背,理解每种类型的存储方式和转换规则
- 多写代码:在控制台实验各种类型操作,加深印象
- 注意陷阱:特别关注容易出错的地方(如
typeof null、[] == ![]) - 使用工具:善用 TypeScript、ESLint 等工具避免类型错误
- 代码规范:始终使用
===,显式类型转换,做好参数校验
面试准备清单
- 能说出 8 种数据类型
- 理解基本类型和引用类型的区别
- 掌握 4 种类型判断方法及优缺点
- 熟悉显式和隐式类型转换规则
- 能解释常见的类型转换陷阱
- 了解 Symbol 和 BigInt 的使用场景
- 能手写类型判断和深拷贝函数