为什么你需要这篇指南?
面试的时候,我们可能会遇到这样的场景:面试官微笑着看着你,然后冷不丁的抛出一个看似简单实则深奥的问题:“你能解释一下JavaScript中的内存管理吗?”或者更直接一点:“JavaScript中有哪些常用的数据结构,它们的优缺点是什么?”这些问题听起来好像很简单,但如果你没有准备充分,很容易被问得一头雾水,甚至怀疑人生,结果与大厂失之交臂。
铁铁 Don't worry,你不是一个人在战斗!在这篇文章中,我们将一起探索JavaScript的数据结构和内存管理,让你在面试中不仅能从容应对,还能轻松反杀,让面试官刮目相看!
让JS的数据类型知识刻入DNA
在开始前,我浅浅问一句:铁铁,你晓得有数据类型分为多少种吗?哎~ 不论知不知道,我们来全部举例出来不就晓得了。
- 简单数据类型:(Primitive)
- JS 中的老牌成员:
number:用于表示数值,包括整数和浮点数。(有误差,精度丢失)string:用于表示和操作文本数据。boolean:用于表示逻辑值,只有两个可能的值:true和false。undefined:表示一个未定义的值,通常用于声明但未赋值的变量或不存在的属性。null:表示一个空值或不存在的对象。
- es6后 的新增成员:
symbol:表示独一无二的标识符。bigint:表示任意大小的整数,解决了number类型在处理大整数时的精度问题。
- JS 中的老牌成员:
- 复杂数据类型:
object(对象):表示复杂的数据结构,可以包含多个属性和方法,是所有引用类型的基类。
聪明的小伙伴就会说了,哎~ JS 中有 8 种数据类型。但是捏,大佬就会发话了,其实里面只有 7 种,因为number和bigint可以合称为numeric,所以我们可以说JS 中有 7 种数据类型
让我们有请今天的几位主角:
- 1. symbol:
在上面我们知道symbol 用于表示独一无二的标识符。但是其也有一些特殊性质,每个 symbol 值都是唯一的,即使使用相同的描述符创建的 symbol 也是不同的。symbol 主要用于对象属性的键,以确保属性名的唯一性,从而避免命名冲突。例如:
console.log(Symbol('1') === Symbol('1')) //false
我们创建了两个symbol,虽然他们都是'1',但是他们并不相等,因为他们具有唯一性.
- 2. bigint:
由于传统的 number 类型在处理非常大的整数时可能会失去精度,因为 number 类型的最大安全整数是 2^53 - 1(即 9007199254740991)。超过这个范围的整数可能会导致精度丢失。而为了解决这个问题,在 ES2020 中引入了 bigint 类型。例如:
// 数字范围有限
// let num1 = 9999999999999999999999;
// let num2 = 8888888888888888888888;
// console.log(num1 + num2); 精度丢失
// bigint 大整数 解决精度丢失 以n结尾
let num1 = 9999999999999999999999n;
let num2 = 8888888888888888888888n;
console.log(num1 + num2);
但是不能解决number中浮点数计算的精度丢失哦,因为bigint只能处理整数.
- 3.null:
null类型表示一个空值或不存在的对象,是一个可以赋给变量的特殊值,并且可以回收内存。哎~ 回收内存?null的这个功有点特殊哦,我们不妨来深挖一下。让我们来看个例子:
let a = null; // 存储在“栈内存”之中
console.log(a);
let largeObject = {
data: new Array(100000000).fill('a') // .fill() 填充数组
} // 存储在“堆内存”之中,但是地址存放在“栈内存”之中
// 释放内存 垃圾回收(直接释放储存在堆内存中的内存)
largeObject = null;
这里我们接触到了两个概念,即栈内存(Stack Memory)和堆内存(Heap Memory),这也是今天的最终boss,让我们来深入了解一下.
JavaScript 的内存管理:
JavaScript 的内存管理是一个复杂但非常重要的主题。理解内存管理机制可以帮助你编写更高效、更稳定的代码,避免常见的内存泄漏问题。其已经成为了我们的必修课,那么我们就来详细了解其原理.
内存分配:
在 JavaScript 中,内存分配主要涉及栈内存(Stack Memory)和堆内存(Heap Memory)
栈内存(Stack Memory) :
栈内存是一种线性的内存结构,遵循后进先出(LIFO, Last In First Out)的原则,按照函数调用的顺序依次分配和释放内存,当函数执行完毕后,其在栈中分配的内存会自动释放。所以其主要用于存储简单的数据类型以及函数调用的上下文信息(如局部变量和函数参数)。但是由于栈内存的大小是固定的,通常较小,因此不适合存储大型数据结构。
- 分配:当函数被调用时,一个新的栈帧会被创建,其中包含函数的局部变量和参数。
- 释放:当函数执行完毕后,对应的栈帧会被销毁,释放其中的内存。
例如:
function add(a, b) {
let result = a + b;
return result;
}
let sum = add(10, 20);
在这个例子中:
add函数被调用时,新的栈帧被创建,包含参数a和b以及局部变量result。- 当
add函数执行完毕后,栈帧被销毁,a、b和result的内存被释放。 sum在栈中分配内存,存储add函数的返回值。
堆内存(Heap Memory):
堆内存是一种非线性的内存结构,用于存储复杂的数据类型。其的分配和释放相对灵活,但速度较慢(动态管理内存),因为灵活这一特点,我们可以用其存储大型数据结构。但是其缺点也是比较明显的,堆内存的管理依赖于垃圾回收机制,需要开发者注意避免内存泄漏。
- 分配:当创建一个对象或数组时,内存会在堆中分配。
- 释放:当对象或数组不再被引用时,垃圾回收机制会释放其占用的内存。 例如:
let largeObject = {
data: new Array(100000000).fill('a') // .fill() 填充数组
} // 存储在“堆内存”之中,地址存放在“栈内存”之中
// 释放内存 垃圾回收(直接释放储存在堆内存中的内存)
largeObject = null;
在这个例子中:
data是在堆内存中分配的,因为无法在栈内存中存储这么大的数组,所以我们会在栈内存中存储堆内存的地址,方便访问。- 栈内存中只存储了指向这些对象的引用(指针)。
- 最后利用垃圾回收机制,用
null来释放内存。
为了能充分理解栈与堆的合作,我们来讲述一个更直观的例子:
let obj = {
name : "西西里",
job : "前端开发工程师",
company : "无"
}
// 拷贝赋值
let a = 1;
let b = a;
b = 3;
// 引用式赋值
let obj2 = obj;
obj2.name = "东东里";
console.log(a,b);
console.log(obj,obj2);
发生了一个严重的事情,那就是我们obj中的name怎么变成了和obj2一样,但是我们并没有对obj进行修改呀,这是因为在对obj2进行赋值时,在栈内存调用的是我们储存复杂数据类型的内存位置的地址,即会查找到堆内存中,而obj2 = obj这一操作是将obj2在栈内存中存储的地址也指向obj存储的地址,所以在对obj2进行修改后,调用obj也会发生改变,不妨结合图解一起理解注释中的 拷贝赋值 和 引用赋值。
现在是不是对这些数据类型和内存管理有了一定了解呢,当然其中的奥义还有许多,读者可以自行去寻找。
而在最后,我们再介绍一个用于查询数据类型的类型运算符。
收下吧,我最后的知识点:(介绍 typeof)
在 JavaScript 中有多种类型,那么为了能获得变量的确切类型,我们可以使用typeof这一运算符,话不多说,上例子:
let a = 1;
// typeof JS 类型运算符
// console.log(typeof a, typeof(a))
console.log(typeof a, + '1');
console.log(typeof "hello");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof Symbol('hello'));
console.log(typeof 1223n);
console.log(typeof null);
console.log(typeof function() {});
我们可以得到以下结果:
那么眼尖的小伙伴就会发现了,这这这给我null干哪去了,这还是 JS 简单数据类型吗?
哎~ 这里就不得不提一个小小的bug了,typeof 除了null之外的primitive 类型,都可以得到正确的结果,但是null的结果是object,这是被夺舍了吗?
其实这是当初 JS 设计时的bug,要知道 JavaScript 是在 1995 年由 Netscape 公司的 Brendan Eich 在短短 10 天内设计完成。当时的 JavaScript 实现是基于 C 和 C++ 的,而这些语言中的 NULL 通常被定义为 (void*)0,即一个指向空地址的指针。但是在 JavaScript 早期设计时将 null 视为一个特殊的对象(object)引用,typeof null 返回 "object" 的行为在 JavaScript 的早期版本中就已经确定。为了保持向后兼容,这个行为在后续的 JavaScript 版本中没有改变,所以才会发生这个所谓的“bug”。
总结
经过这一番深入浅出的讲解,相信你现在已经对 JavaScript 的数据类型和内存管理有了深刻的理解。希望这篇文章能为你查缺补漏,让你在 JS 的“肌肉”更加强大,轻松应对那些看似“灵魂拷问”的问题。
---欢迎各位点赞、收藏、关注,如果觉得有收获或者需要改进的地方,希望评论在下方,不定期更新,都看到这里了,真的忍心不点个赞吗~~~