前端面试必备 | 堆、栈篇(P1-12)
(图片来自网络)
1. JavaScript中的堆和栈有什么区别?
在JavaScript中,堆和栈是两种不同的内存管理方式,用于存储不同类型的数据。
-
堆(Heap): 堆是用于动态分配内存的区域,用于存储引用类型的数据,如对象和数组。在堆中分配的内存不会自动释放,需要通过垃圾回收机制来回收不再使用的内存。堆的大小通常比栈大,并且可以动态增长和收缩。
-
栈(Stack): 栈是用于管理函数执行上下文和存储基本类型值的一种数据结构。每当执行一个函数时,都会在栈中创建一个新的执行上下文,包括函数的参数、局部变量和函数的返回地址。当函数执行完成后,对应的执行上下文会被销毁,栈会自动释放相关的内存。栈的大小通常比较小且固定,内存分配由系统自动管理。
下面是堆和栈的一些区别:
- 存储内容:堆用于存储引用类型的数据,如对象和数组;栈用于存储基本类型的数据,如布尔值、数值和字符串,以及函数执行的上下文。
- 分配方式:堆通过动态分配内存来存储数据;栈通过在执行上下文中的栈帧上分配固定大小的内存来存储数据。
- 大小和生长性:堆的大小通常比栈大,可以动态增长和收缩;栈的大小通常比较小且固定,由系统自动管理。
- 管理方式:堆的内存管理需要使用垃圾回收机制来回收不再使用的内存;栈的内存管理由系统自动处理,通过栈指针的移动来分配和释放内存。
- 生命周期:堆中分配的内存不会自动释放,需要通过垃圾回收来回收内存;栈中的内存由系统自动管理,在函数执行完成后自动释放。
下面是一个简单的表格,用于总结JavaScript中堆和栈的区别:
| 存储内容 | 引用类型的数据 | 基本类型的数据 |
| 分配方式 | 动态分配内存 | 在栈帧上分配固定内存 |
| 大小和生长性 | 大,动态增长和收缩 | 相对较小,固定大小 |
| 管理方式 | 需要垃圾回收来释放内存 | 系统自动处理内存 |
| 生命周期 | 不会自动释放 | 执行完成后自动释放 |
PS:实际上堆和栈的工作方式可能更加复杂,包括具体的内存分配方式、垃圾回收算法和系统实现等方面的差异。
理解堆和栈的区别对于理解JavaScript中的内存管理和性能优化非常重要。堆主要用于存储复杂数据结构和动态分配的对象,而栈则用于管理执行上下文和存储基本类型值。
2. JavaScript中的基本类型和引用类型在堆和栈中的存储方式有何不同?
JavaScript中的基本类型和引用类型在堆和栈中的存储方式有一些不同之处。
1. 基本类型(Primitive Types):
基本类型的值包括布尔值、数值、字符串、null、undefined 和 Symbol。 基本类型的值通常直接存储在栈中,即栈中存储的是实际的值本身。 当我们创建一个基本类型的变量并将一个值赋给它时,该值会直接存储在栈中,当变量不再需要时会自动释放。
例如:
let number = 42; // 将数值 42 存储在栈中
let name = "John"; // 将字符串 "John" 存储在栈中
2. 引用类型(Reference Types):
引用类型的值包括对象、数组和函数。 引用类型的值存储在堆中,而栈中存储的是对堆中对象的引用(即内存地址)。 当我们创建一个引用类型的变量时,实际的对象会被存储在堆中,而变量本身在栈中存储对堆中对象的引用。 由于堆中的对象可能占用的内存较大,因此通过引用来访问它们能够提高内存效率。
例如:
let person = { name: "Alice", age: 25 }; // 在堆中创建一个对象,并将其引用存储在栈中的变量 "person" 中
let numbers = [1, 2, 3, 4, 5]; // 在堆中创建一个数组,并将其引用存储在栈中的变量 "numbers" 中
需要注意的是,当引用类型的值被赋给另一个变量时,实际上是将堆中的引用复制到了新的变量中,而不是创建一个新的对象。这导致多个变量可以引用同一个对象。
总结起来,基本类型的值直接存储在栈中,而引用类型的值存储在堆中,而栈中存储的是对堆中对象的引用。这种存储方式的差异对于理解变量的赋值、传递和比较行为至关重要。
3. 什么时候会在堆上创建对象?什么时候会在栈上创建变量?
在JavaScript中,创建对象和创建变量的位置是由它们的类型确定的。
-
堆上创建对象:
- 引用类型的值(对象、数组和函数)在堆中创建。当我们使用
new关键字创建对象实例,或者使用字面量表示法创建数组和对象时,这些值将在堆上动态分配空间并创建对象。
例如:
let obj = new Object(); // 在堆上创建一个空对象 let arr = []; // 在堆上创建一个空数组 function foo() {} // 在堆上创建一个函数对象 - 引用类型的值(对象、数组和函数)在堆中创建。当我们使用
-
栈上创建变量:
- 基本类型的值(布尔值、数值、字符串、null、undefined 和 Symbol)在栈上创建。当我们声明一个变量并将基本类型的值赋给它时,该值直接存储在栈中的变量位置上。
例如:
let number = 42; // 在栈上创建一个存储数值 42 的变量 let name = "John"; // 在栈上创建一个存储字符串 "John" 的变量
需要注意的是,对于引用类型的值,通过变量进行赋值或传递时,实际上是复制了对堆中对象的引用(内存地址),而不是复制对象本身。
此外,与基本类型不同,引用类型的值可以通过多个变量引用同一个对象。这意味着多个变量可以指向同一个堆上的对象,修改一个变量的值将影响到其他引用该对象的变量。
总结起来,对象在堆上创建,变量在栈上创建。栈上的变量存储基本类型的值,而堆上的对象以引用类型的值的形式存在,变量存储对堆中对象的引用。
4. 在JavaScript中,变量的赋值是存储在栈还是堆中?
在JavaScript中,变量的赋值过程是将值存储在栈中的。
对于基本类型的值,变量直接存储基本类型的值本身,这些值被存储在栈中。当我们将一个基本类型的值赋给一个变量时,该值的副本会被创建并存储在变量所在的栈内存中。
例如:
let number1 = 42; // 将数值 42 存储在栈中的变量 "number1" 中
let number2 = number1; // 将 "number1" 的值复制到 "number2",即创建副本并存储在栈中的新变量 "number2"
对于引用类型的值,变量存储的是对堆中对象的引用(内存地址)。当我们将一个引用类型的值赋给一个变量时,实际上是将堆中对象的引用复制给了变量,而不是创建一个新的对象。
例如:
let obj1 = { name: "Alice" }; // 在堆中创建一个对象,并将其引用存储在栈中的变量 "obj1" 中
let obj2 = obj1; // 将 "obj1" 的引用复制给 "obj2",它们引用堆中的同一个对象
需要注意的是,变量在栈中存储了引用值,而这些引用值指向堆中的实际数据。这个实际数据可以是基本类型的值或者引用类型的对象。
总结起来,变量的赋值过程是将值存储在栈中的,对于基本类型的值,存储的是该值本身,而对于引用类型的值,存储的是堆中对象的引用。
5. 函数的执行环境和活动记录存储在哪个数据结构中?
函数的执行环境和活动记录存储在称为"调用栈"(Call Stack)的数据结构中。
调用栈是一种后进先出(LIFO)的数据结构,用于跟踪当前正在执行的代码的执行环境。
每当函数被调用时,它的执行环境和相关的活动记录会被推入调用栈的顶部(也称为 "压栈"),形成一个新的执行上下文。当函数执行完毕后,它的执行环境和活动记录将从调用栈中弹出(也称为 "出栈"),控制权返回给上一个执行上下文。
通过调用栈,JavaScript可以跟踪函数的嵌套调用、控制代码的流程和管理函数的内部变量。每个执行上下文都包含了函数的变量、参数信息和其
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
前端面试必备知识点:HTML和CSS、JS(变量/数据类型/操作符/条件语句/循环;面向对象编程/函数/闭包/异步编程/ES6)、DOM操作、HTTP和网络请求、前端框架、前端工具和构建流程、浏览器和性能优化、跨浏览器兼容性、前端安全、数据结构和算法、移动端开发技术、响应式设计、测试和调试技巧、性能监测等。准备面试时,建议阅读相关的技术书籍、参与项目实践、刷题和练习,以深化和巩固你的知识。
