为什么需要数据类型?
你可以把数据类型想象成生活中不同的“容器”。比如,装水的杯子、装食物的盘子、装开关的按钮——每种容器都有自己的用途和特点。在 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 (布尔值)
只有 true
和 false
两个值,通常用于逻辑判断,比如开关、条件语句等。
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
就像一个被清空的盒子,明确告诉你里面什么都没有。
小贴士:null
和 undefined
很像,但用途不同。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
(正则表达式)、Map
、Set
等。
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'
这个函数几乎能应对所有场景,简单又强大!
总结:从基础到进阶
- 两大类数据类型:
- 原始类型:
String
, Number
, Boolean
, Undefined
, Null
, Symbol
, BigInt
,值拷贝,简单直接。
- 引用类型:
Object
(包括 Array
, Function
, Date
等),地址拷贝,修改要小心。
- 类型检测:
typeof
:适合快速检查基本类型,但对 null
和对象有局限。
Array.isArray()
:检测数组的首选。
Object.prototype.toString.call()
:最精确的通用方法。
instanceof
:适合检查对象继承关系。
- 实战建议:
- 多用
===
避免类型转换陷阱。
- 调试时用
console.log
检查类型。
- 封装一个
getType
函数,应对复杂场景。
希望这篇文章让你对 JS 数据类型有了全新的认识!下次写代码时,试试用 getType
函数检查变量,或者在遇到奇怪的 bug 时用 typeof
排查问题。数据类型的世界虽然复杂,但一旦掌握,就会让你的编程之路更加顺畅!