JavaScript:从数据存储看基本类型和引用类型的区别

19 阅读4分钟

数据类型

在 JavaScript 里,数据类型可分为基本数据类型引用数据类型

基本数据类型

  1. undefined:当变量已声明但未赋值,或者函数没有返回值时,就会呈现这种类型。
let x;
console.log(x); // 输出:undefined
  1. null:它代表变量的值为空,是一种原始值。
let y = null;
console.log(y); // 输出:null
  1. boolean:该类型只有两个值,即truefalse,常用于逻辑判断。
let isDone = false;
  1. number:此类型用于表示整数和浮点数,包括特殊数值如Infinity-InfinityNaN
let count = 42;
let price = 9.99;
let invalid = NaN;
  1. string:用于表示文本数据,可以使用单引号、双引号或反引号来创建。
let message = "Hello, world!";
  1. symbol(ES6 新增):这是一种独一无二且不可变的数据类型,常被用作对象属性的键。
const id = Symbol('id');
  1. bigint(ES2020 新增):可以表示任意精度的整数,适用于处理非常大的数值。
const bigNum = 123456789012345678901234567890n;

引用数据类型

  1. object:这是一种无序的数据集合,由键值对构成,是 JavaScript 中最基本的引用类型。
let person = {
 name: "Alice",
 age: 30
};
  1. array:用于存储有序的数据集合,其元素可以是不同类型。
let fruits = ["apple", "banana", "cherry"];
  1. function:属于可执行的对象,能够接受参数并返回值。
let add = function(a, b) {
 return a + b;
};
  1. 其他内置对象:像DateRegExpMapSet等都属于此类。
let today = new Date();
let pattern = new RegExp("abc");
let map = new Map();
let set = new Set();

两种类型的区别

基本类型和引用类型最根本的区别就是在数据存储上有所不同,而数据存储不同指的是执行程序时v8引擎会把基本类型和引用类型放在不同的内存空间

内存空间:v8引擎在执行 JavaScript 代码时,开辟出来的内存空间

  1. 代码空间 (代码内容占据的比特文本)
  2. 栈空间 (调用栈)
  3. 堆空间 (存放引用类型数据)

为什么引用类型存储在堆中

调用栈,我们已经很熟悉了,我们都知道因为栈的特性调用栈的内存往往很小,例如Node.js(基于 V8 引擎)的默认栈大小约为 984KB。虽然存取基本类型是够了,但遇到引用类型例如用来存储很多数据的对象、数组就远远不够用了。这就是堆存在的意义,堆的大小只受操作系统和可用内存制约,引用类型存储在堆中大多数情况都不用担心会超出内存大小。

编译执行实际存储情况

让我们通过分析下面程序的编译执行过程了解引用类型在这个过程的实际存储情况。

function foo(person) {
    person.age = 20
    person = {
      name: '李四'
    }
    return person
  }
  let p1 = {
    name: '张三',
    age: 18
  }
  let p2 = foo(p1)
  
  console.log(p1);
  console.log(p2);
  1. 编译全局

image.png

  1. 执行全局,给p1赋值,调用函数foo

image.png

  1. 编译函数,统一形参和实参

image.png

4. 执行函数,先给地址#0001中对象的age赋值,然后指向堆中创建的新对象,最后返回person即#0004

image.png

5.执行全局,p2=#0004

image.png

执行结果

image.png

不同之处在程序中的体现

(1)赋值与引用共享
// 基本类型:值复制
let num1 = 5;
let num2 = num1;
num2 = 10;
console.log(num1); // 输出: 5(num1不受影响)


// 引用类型:引用共享
let arr1 = \[1, 2];
let arr2 = arr1;
arr2.push(3);
console.log(arr1); // 输出: \[1, 2, 3](arr1被修改)
(2)比较操作的差异
// 基本类型:值比较

let a = "hello";
let b = "hello";
console.log(a === b); // 输出: true(值相等)

// 引用类型:引用比较
let obj1 = { key: "value" };
let obj2 = { key: "value" };
let obj3 = obj1;
console.log(obj1 === obj2); // 输出: false(不同对象)
console.log(obj1 === obj3); // 输出: true(同一对象)
(3)函数参数传递
// 基本类型:传递值的副本

function modifyPrimitive(num) {
 num = num \* 2;
}
let x = 10;
modifyPrimitive(x);
console.log(x); // 输出: 10(未修改原始值)


// 引用类型:传递引用的副本
function modifyArray(arr) {
 arr.push(4);
}
let arr = [1, 2, 3];
modifyArray(arr);
console.log(arr); // 输出: \[1, 2, 3, 4](原始数组被修改)
(4)内存管理差异
  • 基本类型:变量作用域结束后自动释放内存。

  • 引用类型:当对象没有任何引用指向它时,垃圾回收机制会回收其内存。

// 对象失去引用后,可能被垃圾回收
let data = { key: "value" };

data = null; // 解除引用,对象可能被回收