众所周知,代码在执行的时候是会进入到对应的代码执行环境的,js代码在执行的时候会形成不同的执行上下文,而这些执行上下文一起就构成了执行栈(执行上下文栈)。
js中有三种代码运行环境(es5):
- 全局上下文(全局环境)
- 函数上下文(函数环境)
- eval运行环境(使用eval()方法运行代码所产生的环境) – 这个一般用的非常少
参考:https://www.w3school.com.cn/jsref/jsref_eval.asp
示例
js代码的执行规律:js代码从上往下执行,首先进入的是全局上下文,然后将全局上下文中的函数提前(函数提升)并构建其函数上下文,当函数上下文构建完毕后,就执行函数里面的后续代码,当函数里面的代码执行完毕后在执行外面的代码,依此类推,从而形成执行栈。
// 全局环境变量a
var a = "全局环境变量a";
// 全局环境运行outer - 先运行,后定义也不会报错,因为同一个上下文中函数将提前(函数提升)创建执行
outer();
// 打印a - 这里a的打印顺序会在outer执行以后
console.log(a);
// 全局函数func
function func() {
var d = 'func函数d';
console.log(d);
}
// 全局函数outer
function outer() {
var b = "outer函数b";
console.log(b);
// outer函数中的inner
function inner() {
var c = "inner函数c";
console.log(c);
// 在inner中运行func
func();
}
// 在outer中运行inner
inner();
}
运行结果:
解读:
运行开始
- 首先创建全局上下文,并且将全局上下文中的函数(outer和func)提升,及a变量提升(此时a为undefined),然后给a变量赋值,执行函数outer,此时进入outer函数。
- 进入outer函数后,创建outer函数上下文,然后将函数(inner)提升,及b变量提升(此时b为undefined),之后给b变量赋值,打印b变量,最后执行函数inner(),此时进入inner函数
- 进入inner函数后,创建inner函数上下文,由于没有函数定义,因此仅对c变量提升(此时c为undefined),然后给c变量赋值,打印c变量,最后执行func函数,此时进入func函数
- 进入func函数后,创建func函数上下文,对d变量提升(此时d为undefined),然后给d变量赋值,打印d变量,整个outer函数执行完毕,按照顺序最后执行打印a变量。
运行结束。
注意:函数提升和变量提升与执行上下文创建的两个阶段有关系。
执行栈的执行顺序如下:
执行上下文的三个重要属性(重点)
1.变量对象(Variable object,VO):变量对象一般包含变量、函数声明、函数的形参这三种内容
2.作用域链(Scope chain):保存这个上下文与其他上下文之间的作用域关系,这也是为什么里面的函数能够调用外面的函数的参数的原因。
3.this指针:
执行上下文创建过程(重点)
执行上下文创建过程有两个阶段:创建阶段及执行阶段。
创建阶段(当函数被调用,但是开始执行函数内部代码之前)
- 创建并设置作用域链Scope chain
- 创建并设置变量对象VO/AO
- 设置this的指向
执行阶段
初始化变量对象,即变量赋值、函数引用,执行代码。
补充:
执行上下文可同时存在多个,但运行时执行上下文只有一个。
关于闭包
https://blog.csdn.net/lxy869718069/article/details/106802023
关于变量提升和函数提升
变量(函数)提升与执行上下文的创建过程有着紧密的联系。
变量(函数)提升:即函数及变量的声明都将被提升到上下文的最顶部,即变量或函数先使用后声明。
// 先使用a变量
console.log(a); // => undefined
// 后声明a变量,并且赋值
var a = 'a变量';
// 打印赋值后的a
console.log(a); // => a
// 使用b函数
b(); // => 打印b
// 后声明b函数
function b(){
console.log('b');
}
关于自执行函数
https://www.jianshu.com/p/ca50b6d0af9b