likes
comments
collection
share

JS之预编译阶段、暂时性死区、垃圾回收机制

作者站长头像
站长
· 阅读数 52

预编译阶段

JS的执行环境分为全局环境和局部环境,所以会产生两种环境变量对象,即全局环境对象和局部环境对象,全局环境对象英文全称为Global Object,简称为GO;局部环境对象英文全称为Activation Object,简称为AO。

JS执行过程可以分为三个步骤:语法分析阶段、预编译阶段、执行阶段。

1.  语法分析阶段:检查书写的JS语法有没有错误,如是否少写一个 "}" 等等。

2.  预编译阶段:

(1)  全局环境预编译步骤:

  • 创建GO对象,程序中无法访问。

  • 寻找全局环境中声明的变量(var/let/const),并作为GO对象的属性名,同时赋值为undefined。

  • 寻找全局环境中声明的函数(function),并将函数名作为GO对象的属性名,将命名函数作为它的值。

(2)  局部环境预编译步骤(代码块、局部函数):

  • 创建AO对象,程序中无法访问。

  • 寻找形参与变量声明,作为AO对象的属性名,值为undefined,同名会覆盖。

  • 形参与实参值统一,也就是实参值会替换AO对象中形参的属性值。

  • 寻找函数声明,函数名作为AO对象属性名,属性值为函数本身。

  • 如果函数名与变量名冲突,函数名声明会将变量声明覆盖。

// 证明声明优先级:实参 > 变量
function fn(a) {
    console.log(a); // 3
    var a = 1;
    console.log(a); // 1
}
fn(3);
// 证明声明优先级:函数 > 实参 > 变量
function fn(a) {
    console.log(a);// function 
    var a = 1;
    function a() {}
    console.log(a); // 1
}    
fn(3);

在预编译阶段中,变量、函数、实参的声明优先级顺序为:函数 > 实参 > 变量。

3.  执行阶段:从上到下,逐行执行,变量赋值过程也在这个阶段完成。

暂时性死区(TDZ)

var a = 1;
{
    a = 2; // 报错
    let a;
}
//**********************************
var b = 123;
if(true) {
    b = 321; // 报错
    let b;
}

上面代码运行会报错,因为在ES6中规定,如果区块中存在let或const命令,这个区块对这些命令声明的变量,从一开始会形成一个封闭的作用域,假如我们尝试在声明前去使用这些变量,就会报错。

垃圾回收机制

GC 即 Garbage Collection ,程序工作过程中会产生很多 "垃圾",这些垃圾是程序不用的内存或者是之前使用过但以后不会再使用的内存,而GC就是负责来回收这些垃圾的。因为它工作在引擎内部,所以对前端来说是相对比较无感的,GC回收垃圾这一整套的引擎执行过程就是我们常说的垃圾回收机制了。

一些概念:

1.  GC:作用是找到内存空间中垃圾内存,回收内存,让这块内存能重新再次被利用。

2.  内存泄漏:Memory Leak,是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统奔溃等严重后果。

3.  活动对象和非活动对象:

var a = {}; // 活动对象
var b = null; // 非活动对象

策略:

1.  标记清除算法:Mark-Sweep,最常用的策略,分为标记和清除两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(非活动对象)的对象销毁回收。

大致过程:

(1)  程序开始运行,引擎在运行GC时,会给内存中所有变量都加上一个标记,假设内存中所有对象都是垃圾,则都会标记为0。

(2)  然后从各种根对象(全局对象window、document等等)开始遍历,把不是垃圾的对象都改成1。

(3)  在清除阶段清理所有标记为0的垃圾,销毁并回收它们所占用得内存空间。

(4)  最后,把所有内存中的对象再次全部修改为0,等待下一轮垃圾回收。

优点:

实现简单,一位二进制就可以进行标记,不必占用太多不必要的内存空间

缺点:

(1)  内存碎片化,指的是会导致空闲的内存不连续,不能合理的分配内存空间给其他对象。

(2)  分配速度慢,再次分配内存空间给其他对象时,需要多次遍历空间链表来寻找合适的内存块。遍历过程也有三种策略,First-fit:找到大于等于内存立即返回;Best-fit:遍历整个空闲链表,返回大于等于的最小分块返回;Worst-fit:遍历整个空间链表,找到最大的分块,然后按新需要分配的内存大小切成两部分,返回合适的那部分,但是会造成更小的碎片化。

补充:

有另一个标记整理算法,是标记清除算法的升级版,主要能解决内存碎片化的问题。

2.  引用计数算法:Reference Counting,它的策略是跟踪记录每个变量值被使用的次数。

大致过程:

var a = new Object(); // 此对象的引用计数为1(a在引用)
var b = a; // 计数为2(a,b在引用)
a = null; // 计数为1
b = null; // 计数为0, 会被GC立即回收

优点:

(1)  能立即被回收,不用去遍历所有的活动对象和非活动对象。不需要像标记清除算法每隔一段时间就进行一次遍历。

(2)  需要一个计数器,计数器会占据很大的内存空间,并且无法预估上限。

3.  Chrome V8 的垃圾回收算法:V8引擎采用的是分代回收策略,这个策略思想和Java中的垃圾回收思想是一样的。目的是区分新生代(临时)和老生代(持久)对象,多回收新生代,少回收老生代,减少每次需要遍历的对象,从而减少每次GC执行的耗时。

1.  分代式垃圾回收:V8中将堆内存分为新生代和老生代两个区域。

2.  并行回收:当GC在主线程执行的时候,开启多个辅助线程同时执行回收工作。

参考


至此,本篇文章就写完啦,撒花撒花。

JS之预编译阶段、暂时性死区、垃圾回收机制

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。 老样子,点赞+评论=你会了,收藏=你精通了。

转载自:https://juejin.cn/post/7174993986929557561
评论
请登录