为什么需要数据类型?

你可以把数据类型想象成生活中不同的“容器”。比如,装水的杯子、装食物的盘子、装开关的按钮——每种容器都有自己的用途和特点。在 JS 中,数据类型决定了数据的存储方式、操作方式以及内存占用方式。搞清楚这些,能让你的代码更高效、更不容易出错。

JS 的数据类型主要分为两大类:

  • 原始类型 (Primitive Types):最基础的数据,直接存储在“栈”内存中,复制时是值的完全拷贝。
  • 引用类型 (Reference Types):复杂数据,实际内容存储在“堆”内存中,变量保存的是指向堆的“地址”,复制时只复制地址。

下面我们逐一拆解这些类型,并通过代码和类比让你彻底明白。


一、原始类型:简单但强大

原始类型是 JS 的基本构建块,直接存储值,操作简单,复制时互不影响。JS 有 7 种 原始类型:

1. String (字符串)

表示文本数据,可以用单引号 (')、双引号 (") 或反引号 (`) 包裹。反引号定义的模板字符串尤其好用,支持多行文本和变量嵌入。

let name = '小明'
let greeting = `你好,${name}!欢迎来到 2025 年!`

console.log(greeting) // 输出: 你好,小明!欢迎来到 2025 年!

类比:字符串就像一张写着文字的便签,内容可以是名字、句子,甚至表情符号。

2. Number (数字)

表示整数或浮点数(小数),JS 不区分这两种,都是 Number 类型。还包括特殊值 NaN(表示“不是数字”)和正负无穷大 (Infinity, -Infinity)。

let age = 25
let price = 19.99
let notNumber = 'abc' / 2

console.log(notNumber) // 输出: NaN
console.log(1 / 0)     // 输出: Infinity

注意NaN 是唯一一个不等于自己的值(NaN !== NaN),这在实际开发中可能会导致意外bug。

3. Boolean (布尔值)

只有 truefalse 两个值,通常用于逻辑判断,比如开关、条件语句等。

let isLoggedIn = true
let hasAccess = false

if (isLoggedIn) {
  console.log('欢迎登录!')
}

类比:布尔值就像一个开关,要么开 (true),要么关 (false)。

4. Undefined (未定义)

表示一个变量被声明但没有赋值,默认值是 undefined

let score
console.log(score) // 输出: undefined

类比undefined 就像一个空盒子,盒子有了,但里面什么都没装。

5. Null (空值)

表示“主动设置的空”,用来明确表达“这里没有值”。

let currentUser = null // 表示当前没有用户

类比null 就像一个被清空的盒子,明确告诉你里面什么都没有。

小贴士nullundefined 很像,但用途不同。undefined 是系统默认给未赋值的变量,而 null 是开发者主动设置的空值。

6. Symbol (符号)

ES6 引入的类型,用来创建独一无二的值,常用于对象属性的键名,避免冲突。

const id = Symbol('userId')
const user = {
  [id]: '12345'
}

console.log(user[id]) // 输出: 12345
console.log(user['userId']) // 输出: undefined,因为 Symbol 不能用字符串访问

类比Symbol 就像一把独一无二的钥匙,只有用这把钥匙才能打开特定的锁。

7. BigInt (大整数)

ES2020 引入,用于表示超出 Number 安全范围的大整数。通过在数字后加 n 或用 BigInt() 创建。

const bigNum = 9007199254740991n
console.log(bigNum + 1n) // 输出: 9007199254740992n

注意BigInt 不能直接和 Number 运算,否则会报错。

类比BigInt 像一个超大的计数器,能数普通 Number 数不了的大数字。


二、引用类型:复杂但灵活

除了原始类型,其他都是引用类型(也叫对象类型)。引用类型的核心特点是:变量存的是内存地址,复制时只复制地址,修改会影响所有指向同一地址的变量。

最常见的引用类型是 Object,它包含以下几种特殊形式:

1. Object (普通对象)

键值对的集合,键通常是字符串或 Symbol,值可以是任何类型。

let person = {
  name: '小红',
  age: 20,
  sayHi: function() {
    console.log('你好!')
  }
}

console.log(person.name) // 输出: 小红
person.sayHi() // 输出: 你好!

2. Array (数组)

一种特殊的对象,键是数字索引,适合存储有序数据。

let colors = ['红色', '绿色', '蓝色']
console.log(colors[0]) // 输出: 红色

3. Function (函数)

函数也是对象,可以被赋值、传递或调用。

function greet() {
  console.log('Hello World!')
}

greet() // 输出: Hello World!

4. 其他内置对象

比如 Date(日期)、RegExp(正则表达式)、MapSet 等。

let now = new Date()
console.log(now) // 输出当前时间,如: 2025-07-11T13:46:00.000Z

关键区别:原始类型复制是值拷贝,互不影响;引用类型复制是地址拷贝,改一个另一个也变。

// 原始类型:值拷贝
let a = 10
let b = a
b = 20
console.log(a) // 输出: 10

// 引用类型:地址拷贝
let obj1 = { value: 10 }
let obj2 = obj1
obj2.value = 20
console.log(obj1.value) // 输出: 20

类比:引用类型就像一个共享的笔记本,A 和 B 都有这个笔记本的“地址”,A 在上面写字,B 翻开时也会看到。


三、如何检测数据类型?

在开发中,判断变量的类型非常重要。JS 提供了多种方法,我们来一一看看它们的用法和注意事项。

1.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('s')) // 'symbol'
console.log(typeof 123n)        // 'bigint'
console.log(typeof function(){}) // 'function'

  • typeof null 返回 'object'(JS 的历史遗留问题)。
  • typeof []typeof {} 都返回 'object',无法区分具体类型。

2.instanceof:适合检测引用类型

检查一个对象是否是某个构造函数的实例,沿着原型链查找。

let arr = [1, 2, 3]
let obj = {}
let date = new Date()

console.log(arr instanceof Array)   // true
console.log(obj instanceof Object)  // true
console.log(date instanceof Date)   // true

注意instanceof 不能检测原始类型,且会受原型链影响。

console.log(arr instanceof Object) // true,因为数组的原型链上有 Object

3.Array.isArray():专门检测数组

最简单可靠的数组检测方法。

console.log(Array.isArray([1, 2, 3])) // true
console.log(Array.isArray({}))        // false

4.Object.prototype.toString.call():最精确的通用方法

能区分几乎所有内置类型,返回类似 [object Type] 的字符串。

const getType = value => Object.prototype.toString.call(value).slice(8, -1).toLowerCase()

console.log(getType('hello'))    // 'string'
console.log(getType(42))         // 'number'
console.log(getType(null))       // 'null'
console.log(getType([1, 2]))     // 'array'
console.log(getType(new Date())) // 'date'

优点:能准确区分 null、数组、对象等,堪称类型检测的“瑞士军刀”。

5.constructor 属性

每个对象都有一个 constructor 属性,指向它的构造函数。

let str = 'hello'
let arr = [1, 2, 3]

console.log(str.constructor === String) // true
console.log(arr.constructor === Array)  // true

注意constructor 可能被修改,不够可靠,谨慎使用。


四、容易踩的坑和实用建议

1. 类型转换的陷阱

JS 的类型转换有时会让人摸不着头脑,比如:

console.log(1 + '1')   // '11'(数字被转为字符串,拼接)
console.log(1 + Number('1')) // 2(显式转换后正常加法)
console.log(1 == '1')  // true(宽松相等会自动转换类型)
console.log(1 === '1') // false(严格相等不转换类型)

建议:始终使用 === 进行比较,避免类型转换带来的意外。

2.NaN 的检测

NaN 是个特殊值,用 Number.isNaN() 检测更严格。

console.log(Number.isNaN(NaN))      // true
console.log(Number.isNaN('abc'))    // false
console.log(isNaN('abc'))           // true(因为会尝试转换)

3. 实际开发中的选择

  • 基本类型:用 typeof 快速检查。
  • 数组:用 Array.isArray(),简单直接。
  • 精确区分:用 Object.prototype.toString.call()
  • 继承关系:用 instanceof 检查原型链。

调试小技巧:不确定变量类型时,多用 console.log(typeof value)console.log(getType(value)) 检查,养成习惯后就能轻松应对各种场景。


五、动手实践:封装一个完美的类型检测函数

结合上面的方法,我们来写一个万能的类型检测函数:

function getType(value) {
  // 先处理 null,因为 typeof null 会返回 'object'
  if (value === null) {
    return 'null'
  }
  // 用 typeof 检查基本类型
  const type = typeof value
  if (type !== 'object' && type !== 'function') {
    return type
  }
  // 对于对象和函数,用 toString 精确区分
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}

// 测试一下
console.log(getType('hello'))    // 'string'
console.log(getType(42))         // 'number'
console.log(getType(null))       // 'null'
console.log(getType([1, 2]))     // 'array'
console.log(getType({}))         // 'object'
console.log(getType(new Date())) // 'date'
console.log(getType(() => {}))   // 'function'

这个函数几乎能应对所有场景,简单又强大!


总结:从基础到进阶

  1. 两大类数据类型
    • 原始类型:String, Number, Boolean, Undefined, Null, Symbol, BigInt,值拷贝,简单直接。
    • 引用类型:Object(包括 Array, Function, Date 等),地址拷贝,修改要小心。
  2. 类型检测
    • typeof:适合快速检查基本类型,但对 null 和对象有局限。
    • Array.isArray():检测数组的首选。
    • Object.prototype.toString.call():最精确的通用方法。
    • instanceof:适合检查对象继承关系。
  3. 实战建议
    • 多用 === 避免类型转换陷阱。
    • 调试时用 console.log 检查类型。
    • 封装一个 getType 函数,应对复杂场景。

希望这篇文章让你对 JS 数据类型有了全新的认识!下次写代码时,试试用 getType 函数检查变量,或者在遇到奇怪的 bug 时用 typeof 排查问题。数据类型的世界虽然复杂,但一旦掌握,就会让你的编程之路更加顺畅!