JS引擎
根据上文的描述,JS源代码是需要经过JS引擎解析、转化之后才执行的。
通常认为,JS引擎主要有两部分组成:
- 内存堆 引用类型实际值、内存分配的地方
- 调用栈 基本类型存储、引用类型地址名存储、代码逻辑执行的地方
源代码进入JS引擎之后,顺序读取代码,按照变量声明、函数执行等不同规则,分配到堆、或者栈内。
内存堆
存储项目引用类型数据的地方, 系统分配的内存,
JS中的引用类型数据,实际值是零散地存在这里面的
事实上,引用类型的存储是分为2部分存储的:
- 真实值存储在内存中, 是系统根据自身情况,内存区哪里有合适的位置,就分配在哪里,没有严格的顺序的,因此说是零散的
- 真实值所在的物理内存地址,这个值是以基本值的形式存储在栈内的
平时代码中的引用类型赋值,就是仅仅把栈内存储的内存地址赋给新变量,就相当于是告诉新变量该值在内存中的位置,需要的时候去取就行,并不是把真正的值传递过去,内存中该值是只有一份的。这也是引起引用问题的原因
执行栈
执行栈,是代码中实际逻辑语句执行的地方,同时项目运行过程中产生的基本类型的值也是存在此处。
引擎会把代码分成一个个可执行单元,然后依次进入执行栈,被执行
那么可执行单元是什么?
可执行单元,标准的说法是执行上下文
JS中,执行上下文
可以是以下几种:
- Global code -----> 全局执行上下文
- Function code -----> 函数执行上下文
- Eval code -----> eval函数执行上下文
这些东西有什么共同点?
全局代码可以看作是一个IIFE(立即执行函数),
函数就是通俗意义上的函数
eval 是可以把传入字符串执行的函数
函数啊, 全部都是函数啊
因此我们可以粗略的理解为: 执行栈里面的东西,都是一个个函数调用
。
- 首先是入口文件的全部JS代码作为一个IIFE,最先入栈被调用
- 然后在实际执行过程中,调用了其他函数,就会顺次被压入栈内执行
因此, JS引擎的示意图可以更新为如下:
单线程
JS是单线程的。 地球人都知道。
什么意思?
JS引擎中,代码执行是在调用栈的里发生的。
栈是一种LIFO(last in first out 后进先出)的数据结构。
只有栈顶的函数会被处理, 处理完成之后弹出栈, 后面的进入栈顶,再被执行...
举个🌰:
如下的JS文件
function first() { second(); } function second() { console.log('log fn'); } first(); ... // 后续操作
JS引擎处理这段代码的步骤如下 (只关注函数调用)
- 整段代码作为一个IIFE,入栈, main()函数调用进入栈顶,开始执行
- 遇到first函数调用, first函数进入栈顶,开始进入first函数体执行 (此时main函数还未执行完毕)
- 进入first函数体之后,遇到调用second函数, 把second函数压入栈顶,进入second函数体执行
- 进入second函数体之后,遇到console.log函数调用, 把console.log压入栈顶执行
- console.log函数打印完毕之后,执行结束, 弹出栈顶
- 此时栈顶是second函数,继续执行second函数体中,console.log之后的代码,发现没有可执行代码了,OK,那就宣告second执行完毕,弹出
- 此时first函数进入栈顶,那就执行first函数体中,second函数调用之后的代码,发现空空如也,那么first函数执行完毕,弹出
- 栈顶main函数执行后续代码
因此JS单线程,指的是在JS引擎中,解析执行JS代码的调用栈是唯一的,所有的JS代码都在这一个调用栈里按照调用顺序执行,不能同时执行多个函数。