补充:通过动图了解JS中的ECStack、EC、VO 和 AO
前言
今天通过探讨下面 JS 代码是如何执行的,来了解 JS 中一些不常见但很重要的知识点
var x = [12, 23]
function fn(y) {
y[0] = 100;
y = [100]
y[1] = [200]
}
fn(x)
console.log(x)
相信大家只用了 1s 就知道答案了,让我们探讨下代码背后是如何执行的吧
执行过程
1、ECStack 、GO、EC、VO、AO
浏览器会在计算机内存中分配一块内存,专门用来供代码执行的栈内存,称作执行上下文栈 (ECStack) 同时会创建一个 全局对象(GO),将内置的属性方法( isNaN
、setInterval
、setTimeout
... )存放到一块单独的堆内存空间,并且使用 window 指向全局对象
在执行代码前,还需要创建一个全局执行上下文( EC(G) ), 创建完成后,进入到栈内存中去执行( 进栈); 在当前全局执行上下文中,因为会创建很多变量并且赋值,所以会创建一个变量对象 VO(Varibale Object) 来进行保存,在函数私有上下文中的变量对象叫做 活动对象 AO(Activation Object) (ps: 每个执行上下文都有一个单独的变量对象)
总结下这些陌生不常见的名词
- 执行上下文栈(ECStack):专门用来供代码执行的 栈内存
- 全局对象(GO):存放内置的属性方法,window 指向
- 全局执行上下文( EC(G) ):页面加载后进栈、销毁后出栈
- 变量对象VO(Variable Object): 存放当前执行上下文中创建的变量和值
- 活动对象AO(Activation Object): 函数私有上下文中的变量对象
2、全局代码执行
当一切准备就绪,就开始从上到下执行代码 (ps: 执行前还会涉及变量提升的问题,这里不进行展开)
var x = [12, 23]
js 在解析这段代码时,会按照以下 3 个步骤
- 先创建一个值
[12, 23]
- 再创建一个变量
x
- 最后将变量与值进行关联
当创建的值是引用类型时,会在堆内存中开辟新的内存空间用了保存值,创建完成后,会将堆内存地址(通常是 16 进制 )保存到栈内存中; 如果创建的值是基本类型时,会直接保存到栈内存中
继续向下执行,
function fn(y) {
y[0] = 100;
y = [100]
y[1] = [200]
}
函数也是属于引用类型,也需要开辟堆内存空间进行保存,不同于数组和对象保存的是键值对,JS会将函数体通过字符串包裹进行保存,同时也会保存函数相关的属性,例如 函数名 name: fn、形参个数 length: 1 等。 同时,更重要的是,创建函数的时候,就定义了 函数的作用域,也就是 等于当前创建函数的执行上下文。 在这个例子中,函数fn 的作用域就是全局执行上下文,标识为 [[scope]]:EC(G)
在函数创建好后,继续向下执行
fn(x)
通过上面的动图了解到,fn 和 x 都指向堆内存地址,所以,fn(x) 相当于 AAAFFF000(AAAFFF111) , 在执行函数体代码之前,我们需要知道的是:
- 每次函数执行,都会创建一个函数私有执行上下文,创建完之后,需要进入到栈内存中去执行,此时,执行栈中的全局执行上下文就会被压入到栈底(压栈)
- 同时,需要创建一个活动对象 AO 存放当前函数执行上下文中创建的变量和值等
再完成函数执行上下文入栈后,接下来会做以下几件事
- 初始化作用域链 scopeChain: 作用域链通常标记为 <当前执行上下文, 函数创建时的作用域> ,而作用域链是为了函数执行过程中,当活动对象中不存在某个变量时,会沿着作用域链向上找到
- 初始化 this 指向: 本例子中,this 等于 window
- 初始化实参集合 arguments
- 形参赋值 y = x = AAAFFF111
- 执行函数体 紧接着就是执行函数体内容,在执行完成后,当前函数的执行上下文就会出栈,退出执行栈,而被压入栈底的全局执行上下文又被推到了栈顶,此时会继续执行全局上下文中的代码
至此,代码执行结束,最终输出的 x 是 [100, 23]
。
总结
以上,就是本篇内容的全部内容了,如有歧义,欢迎大家留言交流 (Ps: 动图使用 keynote
制作,制作不易,大家多多点赞 )