前言
JavaScript
,作为一种广泛应用于网页开发、服务器端编程以及各类前端框架中的动态编程语言,其执行机制是其高效、灵活和强大特性的基础。理解 JavaScript
的执行机制,对于开发者来说至关重要,这不仅有助于优化代码性能,还能有效避免一些常见的陷阱和错误。
一,声明提升
showName()
console.log(myname);
var myname = 'cmx'
function showName() {
console.log('函数showName被执行')
}
在这段代码当中,在使用showName()
方法和输出myname
变量之前,我们还并未定义showName
方法,和变量myname
,所以按照代码从上往下的执行方式,这段代码将会报错,那我们看看代码的运行结果吧
结果是代码正常运行!函数
showname()
可以正常使用定义的方法,变量myname
也已经被定义过了,如果面试官问你为什么,相信很多小伙伴都知道声明提升(在代码执行过程,会将声明的变量往上提升,会将声明的函数集体提升),下面是v8引擎看到的样子
var myname
function showName() {
console.log('函数showName');
}
showName()
console.log(myname);
myname = 'cmx'
那面试官再往下问;为什么存在声明提升这个概念。这时候我们就内心一万个草泥马了,这个是js
的语言特性啊,我怎么知道为什么,这时候我们就要来了解js
的执行机制了
二,执行上下文
首先,要明确的是,JavaScript
代码在执行前,会先经过编译阶段。这意味着,代码并不是一读入就立即执行,而是先被解析和准备,然后才进入执行阶段。这个准备过程,就是为代码创建执行上下文。
执行上下文,可以理解为代码执行的舞台或环境。每个函数或全局代码块都有自己独立的执行上下文,下面是执行上下文的具体结构
可以看到,在执行上下文中将空间规划成三个板块,分别为变量环境,词法环境,整理后可执行的代码,变量环境当中存放的就是我们声明的变量,在这个执行上下文空间内,
js
引擎会读取,编译代码。那么执行上下文中js
引擎的编译过程是怎么样的呢,我们接着往下看
三,调用栈
1,什么是调用栈
调用栈是 JavaScript
引擎用于管理函数调用的一种数据结构。每当一个函数被调用时,它的执行上下文(包括变量、作用域链等)就会被压入调用栈中。当函数执行完毕后,它的执行上下文就会被弹出调用栈。如果调用栈中的函数过多,导致栈空间耗尽,就会抛出“栈溢出”错误
首先,我们要确认在js
引擎当中是否有栈这个结构,我们可以通过下面代码来得知
function text() {
text()
}
text()
这是个奇怪的代码,怎么在text()
方法里调用text()
方法呢,这样就会形成无限循环,那我们来看看代码的输出结果
代码报错了,最大的栈空间超出了最大的栈空间,也就是“栈溢出”,由此可见js
引擎里面一定有一个栈空间,那么这个js
引擎当中的栈叫什么名字呢,js
引擎当中的栈叫调用栈
。
2,调用栈如何存储执行上下文
看下面一段代码
var a = 5
function add(){
var b = 10
return a+b
}
首先,我们知道add()
方法返回的结果为15
,因为在add()
函数内b
为add
函数内的作用域,a
不是add()
函数内的作用域,但是a
是全局作用域,在add()
方法中找不到的时候,函数作用域是可以访问全局作用域的,而全局作用域不可以访问函数作用域,那我们知道为什么函数作用域是可以访问全局作用域吗。这就要看调用栈的存储方式了
在这段代码中,全局代码会先解析,形成全局执行上下文,并存入调用栈当中
然后执行全局代码块时碰到函数代码块,这时add()
函数代码块又会被解析,形成add()
函数上下文并存入调用栈当中
在add
函数上下文中执行到a时,由于在add
函数上下文中找不到a
,所以他会沿着调用栈往下找,在全局上下文中找a
,因此在我们看来,函数作用域可以访问全局作用域,但是全局作用域不能访问函数作用域
四,编译执行过程
我们以下面代码为例
var a = 1
function fn(a) {
var a = 2
function a() { }
var b = a
console.log(a);
}
fn(3)
首先,我们要先创建一个全局执行上下文,找变量声明和函数声明,将变量声明的变量名作为key
,值为undefiend
,将函数声明的函数名作为key
,值为函数体,全局中有一个 var a
为变量声明,有一个function fn()
为函数声明,所以调用栈内添加全局执行上下文,变量环境内添加声明变量,下面是第一步结构
声明结束后开始执行代码,a=1
,然后执行fn()
函数调用栈里面创建函数fn()
执行上下文,下面是第二步结构
然后就开始找函数
fn()
中的变量声明,有var a ,var b
,这是第三步结构
接下来还是在编译阶段,将形参
a
和实参3
进行统一,即实参会将形参覆盖掉,这是第四步结构
在编译的最后阶段才会找函数声明,这里由于函数啊
a()
与变量a
重名,所以会将a
覆盖掉,变为函数体,下面是第五步结构
至此函数
fn()
编译完成,开始执行a=2
,b=a,输出a
,a=2
将前面a()
函数声明覆盖掉了,所以最后的输出为2
,下面是最后一步和它的输出结果
执行完后,执行上下文并不会在调用栈里面停留,而是会被销毁,因此在执行完后,调用栈内又会变的空空如也。
总结
1, 创建执行上下文对象
2,找形参和变量声明,将形参和声明的变量名作为key
,值为undefiend
3,统一形参和实参的值(全局没有改步骤)
4,找函数声明,将函数名作为key
,值为函数体
5,执行代码
学习检验
function fn(a){
console.log(a);
var a =123
console.log(a);
function a(){}
console.log(a);
var b =function (){}
console.log(b);
function d(){}
var d = a
console.log(d);
}
fn(1)
分析出这段代码的输出结果