从v8来看JavaScript代码的运行时环境

226 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

我们执行一段JavaScript代码,只需要把代码丢给v8虚拟机,v8就会执行并且返回给你结果。

在执行JavaScript代码之前,v8就已经准备好了代码的运行时环境,这个环境里包括了堆空间和栈空间,全局执行上下文,全局作用域

,一些内置的api,这些环境统称为运行时的宿主环境,有了这个环境,v8才能执行JavaScript代码,包括解析源码,生成字节码,解释执行或者编译执行这一系列操作。

什么是宿主环境

生物里,宿主就是给病毒等生物提供生存环境等生物,宿主有自己完整代谢系统,而病毒并没有,病毒药完成自我繁殖,就会和宿主共用一套代谢系统,当病毒离开了宿主,就没有任何生命活动,也不难独立自我繁殖。

同样,我们也可以把v8和浏览器的渲染进程关系看成病毒和细胞的关系,浏览器为v8提供web api,全局变量,消息循环系统,而v8核心的是实现了ECMAScript标准,这对于病毒自己的DNA,v8提供了ECMAScript定义的一些对象和一些核心的函数,包括Object,Function,String,另外,v8还提供了垃圾回收,协程等基础内容,当然,这些内容得需要宿主环境等配合。

除了浏览器,Nodejs也是v8的另一个宿主环境,他提供了与浏览器不同的全局对象和api。

宿主给v8提供了很多执行JavaScript所需的基础功能部件,如下

堆空间和栈空间

在chrome中,只要打开一个渲染进程,渲染进程便会初始化v8,同时初始化堆空间和栈空间。

栈空间主要是来管理JavaScript函数调用的。栈是内存中连续的一块空间,同时也是先进后出,函数调用过程中,上下文都会存在栈上,比如原生类型,引用对象的地址,函数执行状态,this。函数执行结束,函数的执行上下文便会销毁(不考虑闭包)

栈空间最大的特点是空间连续,所以栈中每个元素的地址都是固定的,因此栈空间的查找效率很高。但是内存很难分配到一块很大的连续空间,因此v8会对栈的大小做了限制,我们也不能无休止的调用递归函数。

如果有一些占用内存比较大的数据,或者不太需要存储在连续空间的数据,栈就不太合适,所以v8又使用了堆空间。

堆空间是一种树形的存储结构,js中除了原生类型的数据,其他的都是对象类型,比如函数和数组。浏览器中还有windows对象,document对象,这些都是存在堆空间

全局执行上下文和全局作用域

v8初始化了基础的存储空间之和,接下来就要初始化全局执行上下文和全局作用域。

v8开始执行一段可以执行的代码,会生成一个执行上下文,v8用执行上下文来维护当前代码所需要的变量类型和this执行

执行上下文包括三部分,变量环境,词法环境和this指向,比如全局上下文就有window对象,this也默认指向window,当然还有一些api函数,比如setTimeout这些。

词法环境,包含了let和const等变量内容

全局执行上下文在v8的生命周期里不会被销毁,它会一直保存在堆里,因为它往往是要被频繁使用的。

var x=1
function fun(){
  console.log(x)
}

v8执行这块代码过程中,就会在全局执行上下文添加变量x和函数fun

当然,还要注意一下作用域和执行上下文的关系,同一个全局执行上下文中,可能有多个作用域

比如

var x=10
function fun(){
  let a=9
}

这段两个代码执行,会有2个作用域,x会在全局作用域,a会在一个子作用域。

v8调用一个函数,就会进入函数的执行上下文,这时候全局执行上下文和当前的函数执行上下文就形成了一个栈结构。