JavaScript 数据类型详解

344 阅读9分钟

在学习 JavaScript 时,了解其数据类型的分类、内存分配机制以及类型转换规则是非常重要的。本文将从数据类型分类内存分配机制类型检测数据类型转化等方面,深入剖析 JavaScript 的数据类型体系。


1. JavaScript 数据类型概述

JavaScript 的数据类型可以分为两大类:

  • 简单数据类型(Primitive Types)
  • 复杂数据类型(Reference Types)

这两者之间的最大区别在于它们在内存中的存储方式和拷贝方式。接下来,我们将深入探讨这些差异。

1.1 简单数据类型(Primitive Types)

简单数据类型包括:numberstringbooleannullundefinedsymbolbigint

这些数据类型的特点是 存储在栈内存中,并且在进行赋值或拷贝时,会直接复制值,相当于创建一个新的副本。无论我们如何操作这些变量,它们之间都不会互相影响,因为它们是独立的。

简单数据类型的分类
  • 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:表示布尔值,只有两个值:truefalse

  • 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

BigIntnumber 的区别

  • BigIntnumber 的最大区别是,BigInt 不会受到 number最大安全整数限制,而且能够精确表示任意大的整数。
  • BigIntnumber 类型是不同的,它们不能直接进行运算。你不能将 BigIntnumber 混合在一起进行运算,除非将两者转换为相同的类型。

例如:


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)包括:objectarrayfunction 等。这些类型的变量存储的是 内存地址(堆内存),而不是具体的值。多个变量可以引用同一个对象或数组,因此它们是 按引用传递 的。

复杂数据类型的特点
  • 对象(Object) :对象是属性的集合,可以包含各种类型的数据。通过键值对的方式存储数据。例如:{ name: "Alice", age: 30 }
  • 数组(Array) :数组是特殊类型的对象,用于存储多个值。它具有顺序索引,并且是可迭代的。例如:[1, 2, 3, 4]
  • 函数(Function) :函数本质上也是对象。JavaScript 中的函数既是可执行的代码,又是拥有属性和方法的对象。

2. 内存分配机制

JavaScript 中的 栈内存堆内存 是两种不同的内存管理方式,用来存储不同类型的数据。

特性栈内存堆内存
存储内容基本数据类型(如 numberstring 等)引用类型数据(如对象、数组、函数等)
内存分配自动分配和释放,大小固定动态分配和释放,大小不固定
内存管理由编译器自动管理由垃圾回收机制(GC)自动管理
访问速度快,按顺序存储,操作简单相对较慢,需要通过引用查找
存储结构按照函数调用栈的顺序存储(LIFO)无固定顺序,内存分配较为松散
生命周期当函数执行完毕,栈中的数据会被自动销毁只有当没有引用指向堆中的对象时,才会被垃圾回收机制销毁
内存大小限制通常较小,受到操作系统和浏览器的限制通常较大,可以存储更复杂的数据结构

栈内存

  • 栈内存用于存储 简单数据类型numberstringbooleannullundefinedsymbolbigint)。
  • 这些数据在内存中占用固定大小,因此直接存储数据的值。
  • 当变量被复制或传递时,新的变量会复制原始值,而不影响原值。

堆内存

  • 堆内存用于存储 复杂数据类型objectarrayfunction)。
  • 对于复杂类型,变量存储的是指向堆内存位置的引用(地址),而不是值本身。
  • 因此,多个变量可以指向同一块堆内存区域,改变其中一个变量的值会影响到其他变量。

image.png

3. 数据类型拷贝与引用

拷贝方式

  • 简单数据类型:当拷贝一个简单数据类型时,JavaScript 会将值直接复制到新变量中,两个变量互不影响。例如:

    let x = 10;
    let y = x;  // 拷贝
    y = 20;
    console.log(x);  // 10
    console.log(y);  // 20
    

    在这里,xy 是两个完全独立的变量,修改 y 不会影响 x

  • 复杂数据类型:当拷贝一个复杂数据类型时,实际复制的是该对象或数组的 引用地址,而不是数据本身。因此,修改新变量的内容也会影响到原始数据。例如:

    let obj1 = { name: "Alice" };
    let obj2 = obj1;  // 引用
    obj2.name = "Bob";
    console.log(obj1.name);  // "Bob"
    console.log(obj2.name);  // "Bob"
    

    在这个例子中,obj1obj2 指向同一个对象,所以修改其中一个变量的属性会影响到另一个。

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 (类型不同)

总结

image.png