JavaScript 数据类型完全指南

4 阅读7分钟

JavaScript 数据类型完全指南

数据类型是 JavaScript 的基础,也是每个开发者必须掌握的核心概念。本文将系统介绍 JS 的数据类型,包含代码示例、常用方法和高频面试题,帮助你建立完整的知识体系。

前言

JavaScript 是一门弱类型语言,但这并不意味着我们可以忽视类型的重要性。理解数据类型不仅能帮助我们写出更健壮的代码,还能在面试中游刃有余。本文将从基础到进阶,全面解析 JS 数据类型。

一、基本数据类型(Primitive Types)

基本数据类型是 JavaScript 中最基础的数据单元,它们的特点是不可变——一旦创建,值就固定了,想改只能重新赋值。这些类型直接存储在栈内存中,访问速度快。

1. String 字符串

字符串用于表示文本数据,可以使用单引号、双引号或反引号声明。

// 声明方式
const str1 = '单引号字符串'
const str2 = "双引号字符串"
const str3 = `模板字符串,支持变量插值:${str1}`

// 常用方法
const text = 'Hello World'

console.log(text.length)           // 11 - 获取长度
console.log(text.toUpperCase())    // 'HELLO WORLD' - 转大写
console.log(text.toLowerCase())    // 'hello world' - 转小写
console.log(text.includes('World')) // true - 包含检查
console.log(text.startsWith('Hello')) // true - 开头检查
console.log(text.endsWith('World'))   // true - 结尾检查
console.log(text.split(' '))        // ['Hello', 'World'] - 分割
console.log(text.substring(0, 5))   // 'Hello' - 截取
console.log(text.replace('World', 'JS')) // 'Hello JS' - 替换
console.log(text.trim())            // 去除首尾空格

注意:模板字符串(反引号)支持变量插值,是现代 JavaScript 的推荐写法。

2. Number 数字

JavaScript 只有一种数字类型,包含整数和浮点数。还有特殊值 Infinity-Infinity 和 NaN

// 声明方式
const num1 = 42              // 整数
const num2 = 3.14            // 浮点数
const num3 = 1e6             // 科学计数法,等于 1000000

// 特殊值
console.log(Infinity)   // 正无穷
console.log(-Infinity)  // 负无穷
console.log(NaN)        // Not a Number
console.log(0 / 0)      // NaN

// 常用方法
const price = 123.456

console.log(price.toFixed(2))     // '123.46' - 保留小数位
console.log(Math.floor(price))    // 123 - 向下取整
console.log(Math.ceil(price))     // 124 - 向上取整
console.log(Math.round(price))    // 123 - 四舍五入
console.log(Math.max(1, 5, 3))    // 5 - 最大值
console.log(Math.min(1, 5, 3))    // 1 - 最小值
console.log(Math.random())        // 0-1 之间的随机数
console.log(parseInt('123abc'))   // 123 - 转整数
console.log(parseFloat('3.14'))   // 3.14 - 转浮点数
console.log(Number('123'))        // 123 - 转数字

3. Boolean 布尔值

布尔值只有两个值:true 和 false,用于逻辑判断。

// 声明方式
const isActive = true
const isDone = false

// 常见的 falsy 值(会被转为 false)
// false, 0, '', null, undefined, NaN

// 逻辑运算
console.log(true && false)  // false - 与
console.log(true || false)  // true - 或
console.log(!true)          // false - 非

4. Undefined 未定义

变量声明但未赋值时的默认值。

let a  // 声明了,但没赋值
console.log(a)           // undefined
console.log(typeof a)    // 'undefined'

// 函数无返回值时也是 undefined
function noReturn() {}
console.log(noReturn())  // undefined

5. Null 空值

表示"无"、"空"或"未知"的值,是开发者主动声明的值。

const emptyValue = null
console.log(typeof emptyValue)  // 'object' (历史遗留问题)

// 清空对象引用
let obj = { name: 'test' }
obj = null  // 清除引用,便于垃圾回收

6. Symbol (ES6)

创建唯一标识符,主要用于对象属性名。

// 声明方式
const sym1 = Symbol('description')
const sym2 = Symbol('description')
console.log(sym1 === sym2)  // false - 每次都是唯一的

// 作为对象属性名
const obj = {
  [sym1]: '私有数据',
  name: '公开数据'
}

console.log(obj[sym1])  // '私有数据'
console.log(Object.keys(obj))  // ['name'] - 不包含 Symbol 属性

// 常用方法
const sym = Symbol('test')
console.log(sym.toString())      // 'Symbol(test)'
console.log(sym.description)     // 'test' - 获取描述

7. BigInt (ES2020)

表示大于 2^53 - 1 的整数。

// 声明方式
const big1 = 9007199254740991n  // n 后缀
const big2 = BigInt(9007199254740991)

// 超大整数运算
const maxSafe = Number.MAX_SAFE_INTEGER  // 9007199254740991
console.log(maxSafe + 1)        // 9007199254740992 (精度丢失)
console.log(big1 + 1n)          // 9007199254740992n (正确)

// 不能与普通数字混用
// console.log(big1 + 1)  // TypeError
console.log(big1 + BigInt(1))   // 正确

// 常用场景
const bigNum = 123456789012345678901234567890n
console.log(bigNum.toString())  // 转字符串

二、引用数据类型(Object Types)

引用数据类型存储的是内存地址的引用。与基本类型不同,引用类型是可变的。

1. Object 对象

键值对的集合,最常用的引用类型。

// 声明方式
const obj1 = {}
const obj2 = new Object()
const obj3 = { name: 'Alice', age: 25 }

// 常用操作
const person = {
  name: 'Bob',
  age: 30,
  sayHello: function() {
    return `Hello, I'm ${this.name}`
  }
}

// 访问属性
console.log(person.name)        // 'Bob'
console.log(person['age'])      // 30

// 添加/修改属性
person.job = 'Developer'
person['age'] = 31

// 删除属性
delete person.job

// 常用方法
console.log(Object.keys(person))      // ['name', 'age'] - 获取键名
console.log(Object.values(person))    // ['Bob', 30] - 获取键值
console.log(Object.entries(person))   // [['name', 'Bob'], ['age', 30]] - 键值对
console.log(Object.assign({}, person)) // 浅拷贝
console.log({...person})              // 扩展运算符浅拷贝
console.log(JSON.stringify(person))   // 转 JSON 字符串
console.log(JSON.parse('{"name":"Bob"}')) // JSON 转对象

2. Array 数组

有序的值的集合。

// 声明方式
const arr1 = [1, 2, 3]
const arr2 = new Array(1, 2, 3)

// 常用方法
const fruits = ['apple', 'banana', 'orange']

// 访问和修改
console.log(fruits[0])      // 'apple'
fruits[1] = 'grape'         // 修改

// 增删元素
fruits.push('mango')        // 末尾添加
fruits.pop()                // 末尾删除
fruits.unshift('pear')      // 开头添加
fruits.shift()              // 开头删除
fruits.splice(1, 1)         // 从索引1删除1个元素

// 遍历和转换
fruits.forEach((item, index) => console.log(index, item))
const upper = fruits.map(f => f.toUpperCase())  // 映射
const filtered = fruits.filter(f => f.length > 5) // 过滤
const found = fruits.find(f => f === 'apple')    // 查找
const some = fruits.some(f => f.length > 5)      // 是否有满足条件的

// 其他常用
console.log(fruits.includes('apple'))  // true
console.log(fruits.join('-'))          // 'apple-banana-orange'
console.log(fruits.slice(1, 3))        // ['banana', 'orange']
console.log([...fruits].reverse())     // 反转
console.log([...fruits].sort())        // 排序

3. Function 函数

可执行的代码块,也是对象。

// 声明方式
function func1() { return 1 }
const func2 = () => 2
const func3 = new Function('return 3')

// 函数属性
func1.name      // 函数名
func1.length    // 参数个数

// 高阶函数
const multiplier = (x) => (y) => x * y
const double = multiplier(2)
console.log(double(5))  // 10

// 立即执行函数
(function() {
  console.log('立即执行')
})()

4. Date 日期

处理日期和时间。

const now = new Date()
const specific = new Date('2024-01-15')
const timestamp = new Date(1705315200000)

// 常用方法
console.log(now.getFullYear())    // 年
console.log(now.getMonth())       // 月 (0-11)
console.log(now.getDate())        // 日
console.log(now.getHours())       // 时
console.log(now.getMinutes())     // 分
console.log(now.getSeconds())     // 秒
console.log(now.getTime())        // 时间戳
console.log(now.toISOString())    // ISO 格式字符串
console.log(now.toLocaleString()) // 本地化字符串

5. RegExp 正则表达式

用于模式匹配。

const pattern1 = /abc/i  // 字面量
const pattern2 = new RegExp('abc', 'i')

// 常用方法
const text = 'Hello 123 World'

console.log(pattern1.test(text))      // true - 测试匹配
console.log(text.match(/\d+/))        // ['123'] - 提取匹配
console.log(text.replace(/\d+/g, 'X')) // 'Hello X World' - 替换
console.log(text.split(/\s+/))        // ['Hello', '123', 'World'] - 分割

6. Map 和 Set (ES6)

Map: 键值对集合,键可以是任意类型。

const map = new Map()

// 增删改查
map.set('key1', 'value1')
map.set(123, 'number key')
map.set({id: 1}, 'object key')

console.log(map.get('key1'))    // 'value1'
console.log(map.has('key1'))    // true
map.delete('key1')
console.log(map.size)           // 2

// 遍历
map.forEach((value, key) => console.log(key, value))
for (const [key, value] of map) {
  console.log(key, value)
}

Set: 唯一值的集合。

const set = new Set([1, 2, 2, 3, 3])
console.log(set)  // Set(3) {1, 2, 3}

// 操作
set.add(4)
set.delete(2)
console.log(set.has(1))  // true
console.log(set.size)    // 3

// 数组去重
const arr = [1, 2, 2, 3, 3, 4]
const unique = [...new Set(arr)]  // [1, 2, 3, 4]

三、类型判断方法

typeof 操作符

// 基本类型
console.log(typeof 'hello')      // 'string'
console.log(typeof 42)           // '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' (历史遗留)
console.log(typeof {})           // 'object'
console.log(typeof [])           // 'object'
console.log(typeof function(){}) // 'function'
console.log(typeof new Date())   // 'object'

instanceof 操作符

console.log([] instanceof Array)           // true
console.log({} instanceof Object)          // true
console.log(new Date() instanceof Date)    // true
console.log(/abc/ instanceof RegExp)       // true

// 无法判断基本类型
console.log('hello' instanceof String)     // false
console.log(42 instanceof Number)          // false

Array.isArray() 判断是否为数组

console.log(Array.isArray([]))             // true
console.log(Array.isArray({}))             // false
console.log(Array.isArray('[]'))           // false

Object.prototype.toString.call()

最准确的类型判断方法:

console.log(Object.prototype.toString.call('hello'))  // '[object String]'
console.log(Object.prototype.toString.call(42))       // '[object Number]'
console.log(Object.prototype.toString.call(true))     // '[object Boolean]'
console.log(Object.prototype.toString.call(null))     // '[object Null]'
console.log(Object.prototype.toString.call(undefined))// '[object Undefined]'
console.log(Object.prototype.toString.call([]))       // '[object Array]'
console.log(Object.prototype.toString.call({}))       // '[object Object]'
console.log(Object.prototype.toString.call(new Date()))// '[object Date]'

四、高频面试题

1. 基本类型 vs 引用类型的区别

答案

  • 存储位置:基本类型存储在栈内存,引用类型存储在堆内存(栈内存存储引用地址)
  • 赋值行为:基本类型赋值是复制值,引用类型赋值是复制引用地址
  • 比较方式:基本类型比较值,引用类型比较引用地址
  • 不可变性:基本类型不可变,引用类型可变
// 基本类型
let a = 10
let b = a
b = 20
console.log(a)  // 10 (不变)

// 引用类型
let arr1 = [1, 2, 3]
let arr2 = arr1
arr2.push(4)
console.log(arr1)  // [1, 2, 3, 4] (一起变)

2. 判断数组的正确方法

答案Array.isArray() 是最可靠的方法

// 推荐
Array.isArray([])

// 不推荐
[] instanceof Array  // 跨 iframe 有问题
Object.prototype.toString.call([]) === '[object Array]'  // 太繁琐

3. null 和 undefined 的区别

答案

  • undefined 表示"未定义",是变量的默认值

  • null 表示"空值",是主动赋值的值

  • 两者都是 falsy 值

  • typeof null 是 'object'(历史遗留问题)

4. 0.1 + 0.2 !== 0.3 的原因

答案:JavaScript 使用 IEEE 754 标准存储浮点数,存在精度问题

console.log(0.1 + 0.2)  // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3)  // false

// 解决方案
console.log((0.1 + 0.2).toFixed(1))  // '0.3'
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON)  // true
// EPSILON 是 ES6 的一种安全最小值,如果两数相差小于此值,则说明是正确的

5. Symbol 的用途

答案

  • 创建唯一标识符,避免属性名冲突
  • 实现私有属性(Symbol 属性不会被常规方法遍历)
  • 用于元编程(如 Symbol.iterator)
const sym1 = Symbol('key')
const sym2 = Symbol('key')
console.log(sym1 === sym2)  // false

const obj = {
  [sym1]: '私有数据',
  name: '公开数据'
}
console.log(Object.keys(obj))  // ['name'] - Symbol 不会被遍历

6. BigInt 的使用场景

答案:处理超出 Number 安全范围的整数

// Number 最大安全整数
const max = Number.MAX_SAFE_INTEGER  // 9007199254740991

// BigInt 可以处理更大
const big = 9007199254740991n
console.log(big + 1n)  // 正确

// 常用于金融计算、ID 生成等

7. Map 和 Object 的区别

答案

特性ObjectMap
键的类型只能是 String/Symbol任意类型
顺序ES6 后保持插入顺序保持插入顺序
大小需手动计算.size 属性
性能频繁增删性能较差频繁增删性能更好
迭代需用 Object.keys()可直接迭代
const obj = {}
const map = new Map()

// Object 的键只能是字符串
obj[1] = 'one'
obj['1'] = 'one'  // 覆盖上面的值

// Map 可以保持类型
map.set(1, 'one')
map.set('1', 'two')
console.log(map.size)  // 2

8. 类型转换陷阱

答案

// 隐式类型转换
console.log('5' + 1)      // '51' (字符串拼接)
console.log('5' - 1)      // 4 (数学运算)
console.log([] + {})      // '[object Object]'
console.log({} + [])      // '[object Object]'

// 比较陷阱
console.log(0 == '0')     // true
console.log(0 === '0')    // false
console.log(null == undefined)  // true
console.log(null === undefined) // false

// 建议始终使用 === 进行比较

9. 如何判断一个值是否为 NaN

答案

// 错误方法
console.log(NaN === NaN)  // false

// 正确方法
console.log(Number.isNaN(NaN))        // true
console.log(Object.is(NaN, NaN))      // true
console.log(typeof NaN === 'number')  // true

// 注意区别
console.log(Number.isNaN('abc'))      // false
console.log(isNaN('abc'))             // true (会先转换为数字)

10. 基本类型包装对象

答案:基本类型在调用方法时会临时包装成对象

const str = 'hello'
console.log(str.length)  // 5
console.log(str.toUpperCase())  // 'HELLO'

// 等价于
const temp = new String('hello')
console.log(temp.length)
console.log(temp.toUpperCase())
// 但 temp 会被销毁

// 但不能给基本类型添加属性
str.customProp = 'test'
console.log(str.customProp)  // undefined (不会报错,但无效)

五、总结

数据类型速查表

类型typeof说明可变性
Stringstring字符串不可变
Numbernumber数字不可变
Booleanboolean布尔值不可变
Undefinedundefined未定义不可变
Nullobject空值不可变
Symbolsymbol唯一标识不可变
BigIntbigint大整数不可变
Objectobject对象可变
Arrayobject数组可变
Functionfunction函数可变
Dateobject日期可变
RegExpobject正则不可变
Mapobject映射可变
Setobject集合可变

最佳实践

  1. 始终使用 const 声明变量,除非需要重新赋值
  2. 使用 === 进行严格比较,避免类型转换陷阱
  3. 使用 Array.isArray() 判断数组
  4. 处理浮点数时注意精度问题
  5. 使用 Map 替代 Object 当需要任意类型键时
  6. 使用 BigInt 处理超大整数
  7. 理解引用类型和基本类型的区别,避免意外的副作用

数据类型是 JavaScript 的基础,掌握它们对于编写高质量代码至关重要。希望本文能帮助你建立完整的知识体系。