一.前言
在上一篇文章中我们了解到关于js编译原理和作用域相关的知识,知道了在es6之前我们使用var来定义变量会出现变量提升的请何况,呢曾作用于可以访问到外层,但外层不能访问内层的编译原理,那为什么会出现这样的情况? 在js 中的执行机制到底是怎么样子的呢?
1.执行上下文
我们先来看一份var等一变量的代码如下所示:
showName()
console.log(myname);
var myname='zyx'
function showName(){
console.log('函数showName被执行');
}
让我们来思考一下上述代码输出结果应该为什么呢? 让我们来看一下在vscode下运行的结果吧。如下图所示:
我们可以通过输出结果发现函数showName被提前了那该代码应该为如下形式了
var myname
function showName(){
console.log('函数showName被执行');
}
showName()
console.log(myname);
myname='zyx'
那就会出现此疑问了上篇文章说过var会被提前在此代码运行并编译中体现出来了,那为什么函数也被提前了呢?那我们就要引入一个新的概念了叫:执行上下文
执行上下文:在一份代码执行过程中,内存开辟了一个空间为了将编译识别的内容进行存储,而开辟的这片空间就叫执行上下文,而在官方解释下在执行上下文中存储的内容分为两类:变量环境和词法环境。在变量环境里放的就是你声明的一些变量。最后整理出一块可执行代码块,然后执行代码。这就是一份JS代码在执行前进行的操作。该操作可以通过一幅图来表示如下:
在一份代码执行过程中都会经过执行上下文这一过程,而在此过程中到底是如何进行存储和识别使用的呢?在此之前我们要引入一个栈的概念,而上面环境变量,词法环境,以及可执行代码块存储的整个执行上下文存储在调用栈中。
2.栈
我们先来了解一下栈的概念:栈其实就是一种弱化了的数组模式,在数组中我们看可以通过数组已有的方法随意调用,增加,删除,更改它内部的值,相比之下,栈则是只能实现先进先出,后进后出的使用规则,则只能使用数组中特有的几个方法push,pop或者unshift,shift方法.
那上面我所叙述的调用栈到底是怎样的呢,我会通过下图展现调用栈在执行上下文中以何种方式呈现:
当我们知道这一点让我们来一起看一下下面这串代码:
var a = 2
function add(b,c){
return b + c
}
function addAll(b,c){
var d=10
var result = add(b,c)
return a + result + d
}
console.log(addAll(3,6))
那我们就根据上述执行上下文方式来分析代码:首先将此代码存入到一个调用栈中,在对该代码进行编译形成一份执行上下文,对于该代码在全局下编译,则生成一份全局执行上下文,在此全局执行上下文中,我们先来看变量环境:首先我们声明了一个a,a的值应该为undefined,因为赋值语句是执行语句,在编译的时候我们只找我们声明的量。因为代码是先编译再执行的,编译和执行是严格分开的。然后我们定义了两个函数,用键值的方式进行表示,函数名做键,函数体做值,add = function(){},addAll = function(){},然后就没有声明的量了,剩下的都是赋值语句,所以我们把它写到变量环境中去。
开始执行全局代码,将2赋值给a
之后继续编译会调用函数addAll()将3,6传入到函数中去使用,而当我们调用函数时会生成一个新的栈
并执行d=10,resulet=add(b,c),以及传的3和6的参数值写入到addAll函数中去
继续编译会调用add函数综上述描述一样开辟一个add函数的栈并继续编译执行add执行上下文,如下图所示:
是代码接着运行console.log(addAll(3, 6))这句代码,从上到下一一执行add最后返回结果为21
同时注意当一个函数使用完后他的执行上下文会出栈被销毁
3.总结
- 编译总是发生在执行前
- 当代码执行时会生成一份全局上下文入到调用栈
- 当一个函数被调用时,一个新的函数执行上下文被创建并推入调用栈的顶部。
- 当函数执行完成后,它的执行上下文会从调用栈中弹出(销毁),接着返回到之前的执行上下文。