在学习 JavaScript 时,了解其数据类型的分类、内存分配机制以及类型转换规则是非常重要的。本文将从数据类型分类、内存分配机制、类型检测和数据类型转化等方面,深入剖析 JavaScript 的数据类型体系。
1. JavaScript 数据类型概述
JavaScript 的数据类型可以分为两大类:
- 简单数据类型(Primitive Types)
- 复杂数据类型(Reference Types)
这两者之间的最大区别在于它们在内存中的存储方式和拷贝方式。接下来,我们将深入探讨这些差异。
1.1 简单数据类型(Primitive Types)
简单数据类型包括:number、string、boolean、null、undefined、symbol 和 bigint。
这些数据类型的特点是 存储在栈内存中,并且在进行赋值或拷贝时,会直接复制值,相当于创建一个新的副本。无论我们如何操作这些变量,它们之间都不会互相影响,因为它们是独立的。
简单数据类型的分类:
- number:用于表示数字。
在 JavaScript 中,所有的数字(无论是整数还是浮点数)都属于 number 类型,没有单独的 integer 类型或 float 类型。
整数:例如 1, 10, 100
浮动小数:例如 3.14, 2.718, -0.123
特殊数值:如 NaN :表示一个无法计算的值,通常在数学运算出错时返回,Infinity 和 -Infinity:表示正无穷大和负无穷大,通常出现在超出 number 类型能表示的范围时。
精度问题: 由于 JavaScript 使用 64 位双精度浮点数存储数值,在进行浮动小数运算时,可能会出现 精度丢失 问题。尤其是对于非常小或非常大的浮动小数,精度问题尤为突出。
console.log(0.1 + 0.2); // 0.30000000000000004,而不是 0.3
这个问题源于浮动小数在二进制中无法精确表示,因此产生了舍入误差。在需要高精度运算时,可以考虑使用其他方法,如 BigInt 或 第三方数学库。
-
string:用于表示字符串。字符串是字符的集合,如:"hello"、"world"。
-
boolean:表示布尔值,只有两个值:
true或false。 -
null:表示“空值”或“无对象”。它是一个特殊的值,表示变量应当为空或没有指向任何对象。
作用:
初始化空值:null 常用来表示一个变量初始化为空值,后续可以将其赋值为一个有效的对象或数据。
函数返回值:当函数希望返回一个“空”或“无效”的值时,通常使用 null。例如,当查找某个元素失败时,函数可能返回 null。
对象引用清空:当不再需要某个对象时,设置为 null 来清空引用,从而帮助垃圾回收机制回收内存。
let largeObject = {
data: new Array(1000000).fill('a'),
}
//内存释放 垃圾回收
largeObject = null
- undefined:表示“未定义”的值。通常在声明了一个变量但未赋值时,该变量的值为
undefined。
undefined 是 JavaScript 的一个自动赋值值,通常在声明变量时如果没有明确赋值时出现。
null 是开发者显式赋予的,表示变量本来应该有一个对象值,但此时没有。
- symbol:ES6 引入的一种新的原始数据类型,表示唯一且不可变的值。常用于为对象属性生成唯一标识符。
// Symbol es6新增的数据类型 用于创建独一无二的值
//函数一样的外观 但是是个简单数据类型
console.log(Symbol('马斯克'))
console.log(Symbol('马斯克') === Symbol('马斯克'))
- bigint:用于表示非常大的整数,超出
Number类型的安全范围。
使用 BigInt() 构造函数: 可以通过 BigInt() 构造函数将一个数字或数字字符串转换为 BigInt。
```
let bigInt1 = BigInt(123456789012345678901234567890);
console.log(bigInt1); // 输出 BigInt123456789012345678901234567890
```
-
在数字后面加 n: 使用 n 后缀来表示一个 BigInt,这种方式比较简洁。
let bigInt2 = 123456789012345678901234567890n;
console.log(bigInt2); // 输出 BigInt 123456789012345678901234567890n
BigInt 和 number 的区别
BigInt与number的最大区别是,BigInt不会受到number的 最大安全整数限制,而且能够精确表示任意大的整数。BigInt和number类型是不同的,它们不能直接进行运算。你不能将BigInt和number混合在一起进行运算,除非将两者转换为相同的类型。
例如:
let bigIntValue = 123456789012345678901234567890n;
let numberValue = 10;
// 错误:`BigInt` 与 `number` 不能直接进行加法运算
// console.log(bigIntValue + numberValue); // TypeError: Cannot mix BigInt and other types
// 正确的做法是将 number 转换为 BigInt
console.log(bigIntValue + BigInt(numberValue)); // 输出 BigInt 123456789012345678901234567
1.2 复杂数据类型(Reference Types)
复杂数据类型(如 object)包括:object、array、function 等。这些类型的变量存储的是 内存地址(堆内存),而不是具体的值。多个变量可以引用同一个对象或数组,因此它们是 按引用传递 的。
复杂数据类型的特点:
- 对象(Object) :对象是属性的集合,可以包含各种类型的数据。通过键值对的方式存储数据。例如:
{ name: "Alice", age: 30 }。 - 数组(Array) :数组是特殊类型的对象,用于存储多个值。它具有顺序索引,并且是可迭代的。例如:
[1, 2, 3, 4]。 - 函数(Function) :函数本质上也是对象。JavaScript 中的函数既是可执行的代码,又是拥有属性和方法的对象。
2. 内存分配机制
JavaScript 中的 栈内存 和 堆内存 是两种不同的内存管理方式,用来存储不同类型的数据。
| 特性 | 栈内存 | 堆内存 |
|---|---|---|
| 存储内容 | 基本数据类型(如 number、string 等) | 引用类型数据(如对象、数组、函数等) |
| 内存分配 | 自动分配和释放,大小固定 | 动态分配和释放,大小不固定 |
| 内存管理 | 由编译器自动管理 | 由垃圾回收机制(GC)自动管理 |
| 访问速度 | 快,按顺序存储,操作简单 | 相对较慢,需要通过引用查找 |
| 存储结构 | 按照函数调用栈的顺序存储(LIFO) | 无固定顺序,内存分配较为松散 |
| 生命周期 | 当函数执行完毕,栈中的数据会被自动销毁 | 只有当没有引用指向堆中的对象时,才会被垃圾回收机制销毁 |
| 内存大小限制 | 通常较小,受到操作系统和浏览器的限制 | 通常较大,可以存储更复杂的数据结构 |
栈内存:
- 栈内存用于存储 简单数据类型(
number、string、boolean、null、undefined、symbol和bigint)。 - 这些数据在内存中占用固定大小,因此直接存储数据的值。
- 当变量被复制或传递时,新的变量会复制原始值,而不影响原值。
堆内存:
- 堆内存用于存储 复杂数据类型(
object、array、function)。 - 对于复杂类型,变量存储的是指向堆内存位置的引用(地址),而不是值本身。
- 因此,多个变量可以指向同一块堆内存区域,改变其中一个变量的值会影响到其他变量。
3. 数据类型拷贝与引用
拷贝方式:
-
简单数据类型:当拷贝一个简单数据类型时,JavaScript 会将值直接复制到新变量中,两个变量互不影响。例如:
let x = 10; let y = x; // 拷贝 y = 20; console.log(x); // 10 console.log(y); // 20在这里,
x和y是两个完全独立的变量,修改y不会影响x。 -
复杂数据类型:当拷贝一个复杂数据类型时,实际复制的是该对象或数组的 引用地址,而不是数据本身。因此,修改新变量的内容也会影响到原始数据。例如:
let obj1 = { name: "Alice" }; let obj2 = obj1; // 引用 obj2.name = "Bob"; console.log(obj1.name); // "Bob" console.log(obj2.name); // "Bob"在这个例子中,
obj1和obj2指向同一个对象,所以修改其中一个变量的属性会影响到另一个。
4. 数据类型检测
JavaScript 提供了几种方法来检测变量的类型:
4.1 typeof:
typeof 运算符可以用于检测大部分简单数据类型。它返回一个表示数据类型的字符串。然而,它有个漏洞:null 会被错误地判断为 "object"。
console.log(typeof 123); // "number"
console.log(typeof "Hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof null); // "object" (这是一个 bug)
console.log(typeof undefined); // "undefined"
4.2 instanceof:
instanceof 用于检测某个对象是否是某个构造函数的实例,通常用于检查复杂数据类型(如数组或对象)。但是,instanceof 不能用于判断简单数据类型。
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log("Hello" instanceof String); // false
4.3 Object.prototype.toString.call() :
这是一个更加通用且准确的方法,可以用来判断所有类型的数据,包括 null。它会返回类似 [object Type] 的字符串,其中 Type 是数据的类型。
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call({})); // "[object Object]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
5. 数据类型转化
5.1 强制类型转换
JavaScript 提供了强制类型转换的方式,例如使用 Number()、String()、Boolean() 来将其他数据类型转换为基础数据类型:
Number():将字符串或布尔值转换为数字。String():将数字或布尔值转换为字符串。Boolean():将其他类型转换为布尔值。
console.log(Number("123")); // 123
console.log(String(123)); // "123"
console.log(Boolean(0)); // false
5.2 隐式类型转换
JavaScript 还会在某些运算中进行隐式类型转换,如逻辑运算符、关系操作符和相等运算符 == 等:
- 逻辑运算符:
&&、||会将操作数转换为布尔值。 - 关系操作符:在比较操作中,JavaScript 会将非数字值转换为数字进行比较。
- 相等运算符
==:使用==运算符时,JavaScript 会进行类型转换。注意,这与===运算符不同,后者不会进行类型转换。
console.log("5" == 5); // true (字符串会转换成数字)
console.log("5" === 5); // false (类型不同)