挑战坚持学习1024天——前端之JavaScript高级 认知和方向很重要,认知决定能获利多少,方向不对再怎么努力都白搭。
js基础部分可到我文章专栏去看 ---点击这里
Day36【2022年8月29日】
学习重点: JS 内存管理机制(上)
1.JS内存生命周期
不管什么样的编程语言,在代码的执行过程中都是需要给它分配内存的,不同的是某些编程语言需要我们自己手动的管理内存,某些编程语言会可以自动帮助我们管理内存:
JS 的内存生命周期,和大多数程序语言一样,分为三个阶段:
第一步分配内存:分配申请你需要的内存(申请);
第二步内存读写:使用分配的内存(存放一些东西,比如对象等);
第三步内存释放:不需要使用时,对其进行释放;内存的释放。
不同的编程语言对于第一步和第三步会有不同的实现:
手动管理内存: 比如C、C++,包括早期的OC,都是需要手动来管理内存的申请和释放的(malloc和free函数);
自动管理内存: 比如Java、JavaScript、Python、Swift、Dart等,它们有自动帮助我们管理内存;
对于开发者来说,JavaScript 的内存管理是自动的、无形的。
我们创建的原始值、对象、函数……这一切都会占用内存;但是我们并不需要手动来对它们进行管理,JavaScript引擎会帮助我们处理好它;
2.栈内存与堆内存
JavaScript会在定义数据时为我们分配内存。 JS对于原始数据类型内存的分配会在执行时,直接在栈空间进行分配;JS对于复杂数据类型内存的分配会在堆内存中开辟一块空间,并且将这块空间的指针返回值变量引用。
JS 中的数据类型,整体上来说只有两类:基本类型和引用类型。
其中基本类型包括:Sting、Number、Boolean、null、undefined、Symbol、Bigint。这类型的数据最明显的特征是大小固定、体积轻量、相对简单,它们被放在 JS 的栈内存里存储。
而排除掉基本类型,剩下的数据类型就是引用类型,比如 Object、Array、Function 等。这类数据比较复杂、占用空间较大、且大小不定,它们被放在 JS 的堆内存里存储。
2.1 对堆栈的理解
堆和栈分别是不同的数据结构。栈是线性表的一种,而堆则是树形结构。
实例
let a = 0;
let b = "Hello World"
let c = null;
let d = { name: '修言' };
let e = ['修言', '小明', 'bear**'];
以上几种详细存储方式
a —— Number类型 —— 基本类型 —— 栈内存
b —— String类型 —— 基本类型 —— 栈内存
c —— null —— 基本类型 —— 栈内存
d —— Object —— 引用类型 —— 堆内存
e —— Array —— 引用类型 —— 堆内存
具体访问机制及其区别:
从以上图可以看出:abc 直接从栈内存中取到值
而d和e具体如下:
- 从栈中获取变量对应对象的引用(即它在堆内存中的地址)
- 拿着 1 中获取到的地址,再去堆内存空间查询,才能拿到我们想要的数据
Day37【2022年8月30日】
学习重点: JS 内存管理机制(下)
1.垃圾回收机制
因为内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间。在手动管理内存的语言中,我们需要通过一些方式自己来释放不再需要的内存,比如free函数:
-
但是这种管理的方式其实非常的低效,影响我们编写逻辑的代码的效率;
-
并且这种方式对开发者的要求也很高,并且一不小心就会产生内存泄露;
所以大部分现代的编程语言都是有自己的垃圾回收机制:
-
垃圾回收的英文是Garbage Collection,简称GC;
-
对于那些不再使用的对象,我们都称之为是垃圾,它需要被回收,以释放更多的内存空间;
-
而我们的语言运行环境,比如Java的运行环境JVM,JavaScript的运行环境js引擎都会内存 垃圾回收器;
-
垃圾回收器我们也会简称为GC,所以在很多地方你看到GC其实指的是垃圾回收器;
但是这里又出现了另外一个很关键的问题:GC怎么知道哪些对象是不再使用的呢?
-这里就要用到GC的实现以及对应的算法的垃圾回收算法有两种 —— 引用计数法和标记清除法。
1.1引用计数法
当一个对象有一个引用指向它时,那么这个对象的引用就+1;当一个对象的引用为0时,这个对象就可以被销毁掉;这是最初级的垃圾回收算法,它在现代浏览器里几乎已经被淘汰。但是在面试中还会被问到。
这个算法有一个很大的弊端就是会产生循环引用;
1.1.1引用计数原理详解
当我们用一个变量指向了一个值,那么就创建了一个针对这个值的 “引用”:
const students = ['修言', '小明', 'bear']
如图,大家知道赋值表达式是从右向左读的。这行代码首先是开辟了一块内存,把右侧这个数组塞了进去,此时这个数组就占据了一块内存。随后 students 变量指向它,这就是创建了一个指向该数组的 “引用”。此时数组的引用计数就是 1(如下图)。
在引用计数法的机制下,内存中的每一个值都会对应一个引用计数。当垃圾收集器感知到某个值的引用计数为 0 时,就判断它 “没用” 了,随即这块内存就会被释放。
比如我们此时如果把 students 指向一个 null :
students = null
那么 [‘xiuyan’, ‘xiaoming’, ‘bear’] 这个数组所具备的引用计数就会跟着变成 0(如下图),它就变成了一块没用的内存,即将面临着作为 “垃圾” 被回收的命运。
1.2引用计数弊端循环引用详解
function badCycle() {
var cycleObj1 = {}
var cycleObj2 = {}
cycleObj1.target = cycleObj2
cycleObj2.target = cycleObj1
}
badCycle()
在代码第 8 行,我们执行了 badCycle 这个函数。大家知道,函数作用域的生命非常短暂,当函数执行完之后,作用域内的变量也会全部被视作 “垃圾” 进而移除。
但如果咱们用了引用计数法,那么即使 badCycle 执行完毕,cycleObj1 和 cycleObj2 还是会活得好好的 —— 因为 cycleObj2 的引用计数为 1(cycleObj1.target),而 cycleObj1 的引用计数也为 1 (cycleObj2.target)(如下图)。
引用计数法无法识别循环引用场景下的 “垃圾”如果任由 cycleObj1、cycleObj2 这样的变量肆虐内存,那么会产生内存泄漏。
1.2标记清除法
自 2012 年起,所有浏览器都使用了标记清除算法。可以说,标记清除法是现代浏览器的标准垃圾回收算法。 标记清除的核心思路是可达性(Reachability)这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些有引用到的对象,就认为是不可用的对象; 这个算法有两个阶段,分别是标记阶段和清除阶段:
- 标记阶段:垃圾收集器会先找到根对象,在浏览器里,根对象是 Window;在 Node 里,根对象是 Global。从根对象出发,垃圾收集器会扫描所有可以通过根对象触及的变量,这些对象会被标记为 “可抵达”。
- 清除阶段: 没有被标记为 “可抵达” 的变量,就会被认为是不需要的变量,这波变量会被清除
这个算法可以很好的解决循环引用的问题;
实例
function badCycle() {
var cycleObj1 = {}
var cycleObj2 = {}
cycleObj1.target = cycleObj2
cycleObj2.target = cycleObj1
}
badCycle()
badCycle 执行完毕后,从根对象 Window 出发,cycleObj1 和 cycleObj2 都会被识别为不可达的对象,它们会按照预期被清除掉。这样一来,循环引用的问题,就被标记清除干脆地解决掉了。
1.3其他算法优化补充
JS引擎比较广泛的采用的就是可达性中的标记清除算法,当然类似于V8引擎为了进行更好的优化,它在算法的实现细节上也会
结合一些其他的算法。
标记整理(Mark-Compact) 和“标记-清除”相似;
- 不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化;
分代收集(Generational collection)—— 对象被分成两组:“新的”和“旧的”。
-
许多对象出现,完成它们的工作并很快死去,它们可以很快被清理;
-
那些长期存活的对象会变得“老旧”,而且被检查的频次也会减少;
增量收集(Incremental collection)
-
如果有许多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟。
-
所以引擎试图将垃圾收集工作分成几部分来做,然后将这几部分会逐一进行处理,这样会有许多微小的延迟而不是一个大的
延迟;
闲时收集(Idle-time collection)
- 垃圾收集器只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响。
V8引擎详细的内存图堆,V8引擎内存进行了详细的划分
2.今日精进
人生是一场不断蜕变旅途,没有终点。每天持续充电,找寻机会发展副业。
Day38【2022年8月31日】
今日精进
多跟同事沟通避免不必要的错误,做事还要更细心一点,多实践多练习,认真对待每一件事。
Day39【2022年9月1日】
今日精进
多听多思考,多跟别人沟通,有时候换个角度思考问题会有不一样的感悟。
Day40【2022年9月2日】
今日精进
走出舒适圈,每天都要给自己一点压力,不能过的太安逸,有压力才有动力。
Day41【2022年9月3日】
今日精进
人只有觉得后悔的时候说明留给自己努力的时候不多了,趁现在还有机会,全力以赴不给未来留遗憾。
Day42【2022年8月4日】
今日精进
莫为眼前蝇头小利,放弃未来,格局打开,目光发长远,会发现不一样的风景。
参考资料
- JavaScript高级程序设计(第4版)
- MDN
- coderwhy大神资料参考
- 解锁前端面试体系核心攻略
结语
志同道合的小伙伴可以加我,一起交流进步,我们坚持每日精进(互相监督思考学习,如果坚持不下来我可以监督你)。我们一起努力鸭! ——>点击这里
备注
按照时间顺序倒叙排列,完结后按时间顺序正序排列方便查看知识点,工作日更新。