不变与可变的矛盾:const背后的内存栈堆双舞台

268 阅读3分钟

前言

在JavaScript的发展历程中,ES6引入的const关键字为我们提供了声明常量的能力,弥补了ES5只有var的不足。然而,当我们深入了解const时会发现,它对不同数据类型的处理方式存在显著差异。本文将深入剖析const的工作原理,探讨其与内存栈、内存堆之间的关系,帮助你彻底理解这一重要特性。

一、const的本质:内存地址不变

首先,我们需要明确一个概念:const保证的是变量指向的内存地址不变,而非内存地址中的内容不变

const age = 18; // 简单数据类型
const friends = []; // 复杂数据类型

二、简单数据类型 vs 复杂数据类型

1. 简单数据类型(原始值)

当使用const声明简单数据类型(如Number、String、Boolean等)时:

const age = 18;
age = 20; // 错误!TypeError: Assignment to constant variable.

image.png 为什么会报错?因为简单数据类型的值直接存储在栈内存中,修改值意味着改变内存地址,而const不允许这样做。

2. 复杂数据类型(引用值)

const friends = [
  {
    name: '吴老板',
    hometown: '上饶',
    college: '湖南工业大学',
  }
];

// 可以执行的操作
friends.push({name: '谢老板', hometown: '赣州'});

// 不可执行的操作
friends = []; // 错误!TypeError: Assignment to constant variable.

image.png 对于复杂数据类型,const只保证引用地址不变,但不限制对其内容的修改。

三、内存机制解析

1. 内存栈与内存堆

JavaScript的内存分配主要在两个区域:

  • 内存栈:连续的内存空间,空间小但访问速度快
  • 内存堆:不连续的内存区域,空间大但访问相对较慢

2. 不同类型的存储方式

  • 简单数据类型:直接存储在栈内存中
  • 复杂数据类型:引用地址存储在栈内存中,实际数据存储在堆内存中

四、代码实例分析

让我们通过一个实例更深入地理解:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">



    <title>朋友们</title>


</head>
<body>
    
<script>
const age = 18;
const friends = [
    {
        name: '吴老板',
        hometown: '上饶',
        collage: '湖南工业大学',

    },
    {
        name: '谢老板',
        hometown: '赣州',
        
    },      
];
friends.push({
    name: '邓老板',
    hometown: '宜春',
    
})
const newFriends = friends;
newFriends.push({
    name: '宋老板',
    hometown: '宜春',
})
newFriends.push({
    name: '严老板'
    
})
</script>

</body>
</html>

在这个例子中:

  1. friends变量的内存地址保持不变
  2. 但我们可以修改该地址指向的堆内存中的内容(添加、修改数组元素)
  3. newFriendsfriends指向同一个内存地址(引用传递)

image.png

五、const的内存实现原理

当我们使用const声明变量时,JavaScript引擎会:

  1. 在内存栈中创建一个不可修改的内存空间
  2. 对于简单数据类型,直接将值存入该空间
  3. 对于复杂数据类型,将引用地址存入该空间,实际数据存储在堆内存中

这也解释了为什么const定义的复杂数据类型可以修改其内容:因为栈内存中存储的引用地址没有改变,改变的只是堆内存中的数据。

六、实用建议与最佳实践

  1. 想要保护复杂数据类型:使用Object.freeze()

    const friends = Object.freeze([{name: '吴老板'}]);
    friends[0].name = '谢老板'; // 严格模式下会报错
    
  2. 声明普通变量:优先使用const,需要改变时再用let

    // 优先使用const,增强代码可读性
    const API_URL = 'https://api.example.com';
    
  3. 避免使用var:容易造成变量提升和全局污染

    // 不推荐
    var name = '吴老板'; // 会挂在window对象上
    
    // 推荐
    const name = '吴老板'; // 不会污染全局变量
    

总结

const关键字是ES6中重要的变量声明方式,它的特点是:

  • 对于简单数据类型:值不可变
  • 对于复杂数据类型:引用地址不可变,但内容可变
  • 在内存栈中的值永远不会发生改变

理解const与内存之间的关系,对于我们编写更加健壮、可维护的JavaScript代码至关重要。希望本文能帮助你深入理解JavaScript的内存机制与变量声明的内在联系。