未找到匹配的笔记

1.26 - 数据类型、类型判断、类型转换

JavaScript闭包面试

JavaScript 数据类型完全指南

目录

  1. 数据类型概述
  2. 基本数据类型详解
  3. 引用数据类型详解
  4. 类型判断方法
  5. 类型转换机制
  6. 面试高频考点
  7. 实战技巧

数据类型概述

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

类型判断方法对比表

方法nullundefinedstringnumberbooleansymbolbigintobjectarrayfunction
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('')0
  • Number(' ')0
  • Number(true)1
  • Number(false)0
  • Number(null)0
  • Number(undefined)NaN
  • Number([])0
  • Number([n])n
  • Number([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

学习建议

  1. 理解原理:不要死记硬背,理解每种类型的存储方式和转换规则
  2. 多写代码:在控制台实验各种类型操作,加深印象
  3. 注意陷阱:特别关注容易出错的地方(如 typeof null[] == ![]
  4. 使用工具:善用 TypeScript、ESLint 等工具避免类型错误
  5. 代码规范:始终使用 ===,显式类型转换,做好参数校验

面试准备清单

  • 能说出 8 种数据类型
  • 理解基本类型和引用类型的区别
  • 掌握 4 种类型判断方法及优缺点
  • 熟悉显式和隐式类型转换规则
  • 能解释常见的类型转换陷阱
  • 了解 Symbol 和 BigInt 的使用场景
  • 能手写类型判断和深拷贝函数