栈空间和堆空间
-
(静态语言、动态语言)(强类型语言、弱类型语言)
在声明变量之前需要先定义变量类型,我们把这种在使用之前就需要确认其变量类型的成为称为静态语言,相反地,我们把运行过程中需要检查数据类型的语言称为动态语言。
支持隐式类型转换的语言称为弱类型语言,不支持隐式类型转换的语言称为强类型语言(隐式类型转换:在赋值过程中,变量的赋值可以转化类型)
-
JavaScript的类型
-
内存空间
在JavaScript执行过程中,主要有三种内存空间,分别是代码空间、堆空间、栈空间
- 代码空间主要是存储可执行代码
- 栈空间就是我们经常提及的调用栈,用来存储执行上下文(对象类型是存放在堆空间的,在栈空间中只是保留了对象的引用地址)
原始类型的数据值都是直接保存在“栈”中,引用类型的值是存放在“堆”中。堆栈区分也是有原因的,不能所有的数据都放在栈中,栈是监管着执行上下文的,如果都放在栈中会影响到上下文切换的效率,进而影响到整个程序的执行效率
-
闭包的存储(问题提出:函数调用后,函数的执行上下文就会被销毁,其变量就会被销毁,为啥闭包中中的函数变量还存储在内存中,它存储在哪里)
案例代码: function foo() { var myName = " 极客时间 " let test1 = 1 const test2 = 2 var innerBar = { setName:function(newName){ myName = newName }, getName:function(){ console.log(test1) return myName } } return innerBar } var bar = foo() bar.setName(" 极客邦 ") bar.getName() console.log(bar.getName()) 输出结果 1 1 极客邦 执行存储过程: 1、当 JavaScript 引擎执行到 foo 函数时,首先会编译并创建一个空执行上下文 2、编译过程中,遇到内部函数setName, JavaScript引擎还要对内部函数做一次快速的词法 扫描,发现了内部函数引用了foo函数的 myName 变量,判断这是一个闭包, 于是在堆空间中创建一个“closure(foo)”的对象 (这是一个内部对象,JavaScript 是无法访问的),用来保存 myName 变量 3、接着继续扫描到 getName 方法时,发现该函数内部还引用变量 test1, 于是 JavaScript 引擎又将 test1 添加到“closure(foo)”对象中。 这时候堆中的“closure(foo)”对象中就包含了 myName 和 test1 两个变量了。 4、由于 test2 并没有被内部函数引用,所以 test2 依然保存在调用栈中。
产生闭包的核心有两步:第一步是需要预扫描内部函数;第二步是把内部函数引用的外部变量保存到堆中
垃圾回收
-
不同语言的垃圾回收策略是不一样的,垃圾回收分为自动回收和手动回收策略,例C/C++是手动回收策略,JavaScript、Java、Python 等语言是通过垃圾回收器自动回收垃圾数据
-
调用栈中的数据是如何回收的(ESP:记录当前执行状态指针,下图的下移操作就是销毁showName函数执行上下文的过程)
function foo(){
var a = 1
var b = {name:" 极客邦 "}
function showName(){
var c = " 极客时间 "
var d = {name:" 极客时间 "}
}
showName()
}
foo()
-
堆中的数据是如何回收的 【我们经常说的V8的垃圾回收机制其实在回收堆数据,回收栈数据用到了ESP,esp直接抹掉效率还高】
V8中会把堆分为新生代和老生代两个区域。新生代中存放的是生存时间短的对象,老生代存放的是生存时间长的对象。新生区通常只支持 1~8M 的容量,而老生区支持的容量就大很多了。对于这两块区域,V8 分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收。 -
副垃圾回收器,主要负责新生代的垃圾回收。
-
主垃圾回收器,主要负责老生代的垃圾回收。
-
垃圾回收器的执行流程: 1、标记 2、回收 3、整理
-
副垃圾回收器主要负责新生区的垃圾回收。而通常情况下,大多数小的对象都会被分配到新生区,所以说这个区域虽然不大,但是垃圾回收还是比较频繁的【新生代空间对半划分为对象区域,空闲区域,回收流程:标记、清理、复制到空闲区域进行整理】
-
主垃圾回收器主要负责老生区中的垃圾回收。除了新生区中晋升的对象【经过两次垃圾回收依然还存活的对象,会被移动到老生区中】,一些大的对象会直接被分配到老生区。因此老生区中的对象有两个特点,一个是对象占用空间大,另一个是对象存活时间长。【回收流程:标记、清理、整理】
编译器和解释器
C/C++、GO 等都是编译型语言,Python、JavaScript 等都属于解释型语言。
-
抽象语法树(AST)
1、Babel是一个被广泛使用的转码器,它的工作原理就是将ES6的源码转换为AST,再将ES6的AST转换为ES5的AST,最后利用ES5的AST生成JavaScript源代码
2、ESLint 是一个用来检查 JavaScript 编写规范的插件,其检测流程也是需要将源码转换为 AST,然后再利用 AST 来检查代码规范化的问题
-
生成AST的两个阶段
1、分词(词法分析):其作用是将一行行的源码拆解成一个个 token。所谓token,指的是语法上不可能再分的、最小的单个字符或字符串。你可以参考下图来更好地理解什么 token。【从图中可以看出,通过
var myName = “极客时间”简单地定义了一个变量,其中关键字“var”、标识符“myName” 、赋值运算符“=”、字符串“极客时间”四个都是 token,而且它们代表的属性还不一样】2、解析(语法分析):其作用是将上一步生成的 token 数据,根据语法规则转为 AST。如果源码符 合语法规则,这一步就会顺利完成。但如果源码存在语法错误,这一步就会终止,并抛出一个“语法错误”。
-
JavaScript 的性能优化
- 提升单次脚本的执行速度,避免 JavaScript 的长任务霸占主线程,这样可以使得页面快速响应交互;
- 避免大的内联脚本,因为在解析 HTML 的过程中,解析和编译也会占用主线程;
- 减少 JavaScript 文件的容量,因为更小的文件会提升下载速度,并且占用更低的内存。
V8 是如何执行一段 JavaScript 代码的:V8 依据 JavaScript 代码生成 AST 和执行上下文,再基于 AST 生成字节码,然后通过解释器执行字节码,通过编译器来优化编译字节码。