执行上下文
定义
执行上下文:javascript 代码解析和执行时所在的环境。
我们时刻使用函数,通过函数实现各种逻辑处理,函数之间可以实现调用,当函数调用结束时,程序会通过执行上下文回到函数调用的位置。
执行上下文可以分为全局执行上下文和函数执行上下文,执行上下文都有两个阶段:创建阶段和执行阶段
1.全局执行上下文:
- 创建阶段:
在执行全局代码前创建,
- 创建全局执行对象:浏览器端就是window,node中是global
- 对全局数据进行预处理:
- var定义的全局变量==>undefined, 添加为window的属性(变量提升)
- function声明的全局函数==>赋值(fun), 添加为window的方法 (函数提升)(1.2属于创建全局对象)
- 确定this指向==>赋值(window)
- 执行阶段: 执行全局代码
全局上下文被压入执行栈底(后面介绍),逐行执行代码,执行到函数调用部分进入函数的执行上下文,函数执行完后出栈,以此类推 全局代码执行结束,全局的执行上下文对象销毁
2.函数执行上下文:
函数每次被调用一次,就会创建一个函数执行上下文
函数的数据每次调用都是互相独立,属于各自的执行上下文对象
函数调用结束,执行上下文对象就被销毁了
-
创建阶段:在调用函数的时候,执行函数内的语句之前,创建函数的执行上下文
-
对函数内的数据进行预处理:
- 给形参赋值(实参),作为执行上下文对象的属性
- 给arguments赋值(实参的集合),作为执行上下文对象的属性
- 使用var 声明的变量,提升(赋值 undefined),作为执行上下文对象的属性
- 使用 function 声明的函数提升(值),作为执行上下文对象的方法
- 确定this的指向,this指向调用函数的对象,全局直接调用this指向window
-
执行阶段: 执行函数内的语句
3.执行栈
栈存储结构:先进后出
用来存储执行上下文
- 首先创建全局执行上下文, 压入栈底
- 每当调用一个函数时,创建函数的函数执行上下文。并且压入栈顶
- 当函数执行完成后,会从执行上下文栈中弹出,js引擎继续执栈顶的函数。
如以下函数执行时的执行栈变化为:
function fun1(){
console.log('func1')
fun2()
}
function fun2(){
console.log('func2')
}
fun1()
/*
* fun2
* fun1 fun1 fun1
* global => global => global => global => global
*/
4.执行上下文生命周期 (对照1.2理解创建阶段
ES5 规范去除了 ES3 中变量对象和活动对象,以词法环境组件( LexicalEnvironment component) 和 变量环境组件( VariableEnvironment component) 替代。
创建阶段:
此阶段执行上下文会执行以下操作
- 确定 this 的值,也被称为 This Binding
- LexicalEnvironment(词法环境) 组件被创建
- VariableEnvironment(变量环境) 组件被创建
执行阶段
- 变量赋值。函数引用,执行其他代码逻辑
- 当执行完毕后。执行上下文出栈,等待垃圾回收机制回收
销毁阶段
作用域
作用域概念可以分为全局作用域和块级作用域(包含函数作用域)
1. 全局作用域
变量提升:
- 使用var关键字声明的变量,会在所有代码执行之前声明
- a的输出为undefined
- 但是如果不使用var关键字,变量不会被声明提前
- 如果用let关键字:报错
Cannot access before initialization
- 如果用let关键字:报错
函数的声明提前
- 使用函数声明创建的函数:在所有代码执行之前就被创建 且被存储
- 使用函数表达式创建的函数:不会被提前创建,函数变量是undefiend但是不能调用
2. 函数作用域
1.定义 ⭐
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是 其声明时所在的作用域 ,与其运行时所在的作用域无关--阮一峰JS
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
2.作用域链:
var a=10;
function fun1(){
var b=20;
var a="fun1 a"
function fun2(){
console.log(a)
}
fun2()
}
fun1();
3.省略关键字的情况
var c=33;
function fun(){
console.log(c);
c=10
}
fun();
//console.log(c);
c=10
输出33,因为没有用var定义 没有变量提升,找到全局的c输出var c=10
输出undefined 变量提升let c =10
报错var c
输出undefined
上面的code中在全局中console.log(c)
结果是10 在函数中直接赋值变量都会变成全局变量
4.形参相当于在函数作用域中声明变量
var e =23;
function fun(e){
console.log(e)
}
fun();
函数的调用结果是undefined,传入形参e相当于在函数作用域中声明 var e;
3.块级作用域
{} 会创建块级作用域,具有块级作用域的变量用 let 或 const 声明
- if 语句
- switch 语句
- for 语句
- while 语句
- 直接在 {} 中写代码
4.闭包
函数外无法读取函数内的变量(作用域链),当f1嵌套f2时,想要f2读取f1中的变量,要解决这个问题可以将f2作为其返回值,此时f2就是闭包:
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
闭包的用处有3个:
- 一个是可以读取外层函数内部的变量,
- 另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
- 闭包的另一个用处,是封装对象的私有属性和私有方法。 阮一峰JS
3.作用域和执行上下文的关系⭐
- 函数作用域是在函数声明的时候就已经确认,函数执行上下文是在 函数调用时确定
- 但是函数中的变量是在调用的时候创建
区别:
- 创建的时间点不同:
作用域 | 执行上下文 | |
---|---|---|
函数: | 函数定义的时候 创建函数作用域 | 函数调用时 创建执行上下文 |
全局: | 页面打开时创建 | 在全局作用域确定之后 ,代码执行之前创建 |
- 属性不同:
- 作用域是静态的,只要函数确定好了就一直存在,而且不会变化
- 上下文环境是动态的
参考资料:
- JS现代
- 阮一峰 javascript.ruanyifeng.com/grammar/fun…
- 付铭:learn.fuming.site/front-end/J…
- 带你彻底搞懂执行上下文 www.dazhuanlan.com/2019/12/16/…