js中执行上下文是一个非常重要的概念,理解了执行上下文,可以更好的理解this执行,作用域,闭包等概念 首先要弄清楚几个概念,什么是执行上下文?什么是执行上下文栈?
在ES3中,JavaScript执行上下文是指在代码执行期间创建的环境,其中包含了变量、函数、对象等信息。每个执行上下文都有一个与之关联的变量对象,用于存储该上下文中定义的变量和函数,在ES3中,执行上下文的创建和销毁是由JavaScript引擎自动管理的,开发者无法手动干预。同时,ES3中的执行上下文机制相对简单,没有ES6中的诸多特性,如块级作用域、let和const关键字等。
在ES5中,执行上下文是指JavaScript代码在运行时创建的一个环境,用于管理函数调用、变量声明、作用域链等。每个执行上下文都有一个与之关联的变量对象,用于存储该上下文中定义的变量和函数。在函数执行时,会创建一个新的执行上下文,并将其添加到执行上下文栈中。当函数执行完毕后,该执行上下文会被弹出栈,并销毁。 在ES5中,执行上下文分为三种类型:全局执行上下文、函数执行上下文和eval执行上下文。全局执行上下文是在代码执行之前创建的,而函数执行上下文和eval执行上下文是在函数调用或eval调用时创建的。在ES5中,执行上下文的创建和销毁是由JavaScript引擎自动管理的,开发者无需手动干预。
在ES6中,执行上下文被定义为一个抽象的概念,用于描述JavaScript代码在执行时的环境和状态。每当JavaScript代码开始执行时,都会创建一个新的执行上下文,并将其添加到执行上下文栈中。执行上下文栈是一个后进先出(LIFO)的数据结构,用于跟踪当前正在执行的代码的上下文。
执行上下文分为三种:
全局执行上下文:当进入全局代码时会进行编译,在编译中创建全局执行上下文,并生成可执行代码
函数执行上下文:执行代码的过程中,如果遇到函数调用,会编译函数内的代码和创建函数执行上下文,并创建可执行代码
eval执行上下文:当使用eval函数的时候,eval的代码也会被编译,并创建执行上下文
一段js代码的执行过程是先编译,再执行,在代码编译阶段生成执行上下文
执行上下文包含三个重要的组成部分:
1.变量环境(Variable Environment):包含了当前执行上下文中的变量、函数声明和函数参数等信息。
2.词法环境(Lexical Environment):与变量环境类似,但是还包含了当前执行上下文所处的词法作用域。
3. this 值:指向当前执行上下文所属的对象。
来看一个例子
console.log(foo)
if(false){
var foo = "foo"
}
打印结果:undefined
分析:首先创建全局执行上下文,压入执行上下文栈中,对js代码进行编译,收集所有的var声明并在全局对象中创建,初始化为undefined,接着执行js代码,首先会去全局scope中找foo变量,找不到再去全局对象中去找,此时的foo值为undefined,因为false,所以不会给foo赋值,所以打印结果为undefined
注意:在全局上下文中,所有的非函数var声明(就是不是在函数内部的var声明)以及所有的顶级函数声明都存储在全局对象中,所有的非函数let const class声明存储在全局scope中,首先会在全局scope中去找,找不到再去全局对象中去找,var声明的变量在创建的时候初始化为undefined,let const创建的时候不初始化
看一个例子:
let ls = "woshisui"
console.log(ls) //woshisui
console.log(window.ls)//undefined
分析:因为js代码先会解析代码,首先创建全局上下文,压入上下文执行栈中,收集所有的let变量存储在全局scope中,但是不给ls赋值,接下来执行js代码,把"woshihui"赋值给ls,在全局scope中找到ls并打印,但是全局对象中并没有ls,所以window.ls结果是undefined
var ls = "woshisui"
console.log(ls) //woshisui
console.log(window.ls)//woshisui
分析:因为js代码先会解析代码,首先创建全局上下文,压入上下文执行栈中,收集所有的var变量存储在全局对象中,初始化ls的值为undefined,接下来执行js代码,把"woshihui"赋值给ls,在全局scope中找ls,找不到接着去全局对象中去找,所以window.ls结果是woshihui
函数执行上下文
看一个例子
var a =10
function foo(){
console.log(a)
let a
}
foo()
打印结果:caught ReferenceError: Cannot access 'a' before initialization
分析:首先创建全局上下文,并压入执行栈,js代码进行编译阶段,找到var 声明的 a变量,在全局对象中创建并初始化为undefined,函数foo创建在全局对象中,初始化为一个对象,这个对象保存了当前的上下文环境,再创建一个函数foo的执行上下文,找到里面的let 声明的 a变量,在foo的词法环境中创建并未初始化,调用foo函数,打印a的值,首先会在函数的词法环境中去找,找到未初始化的a,所以就会报错,这里称为暂时死区
看一个例子
function foo(){
console.log(a)
}
function bar(){
var a = 3;
foo()
}
var a = 2;
bar();
打印结果:2
分析:首先创建全局执行上下文,在全局对象创建a初始化为undefined,创建foo,bar为函数对象,调用函数bar,创建bar的执行上下文,并在bar的执行上下文中创建a初始化为undefined,调用foo函数,创建foo的执行上下文,打印a的值,首先会在foo的执行上下文中找,没找到,继续在全局对象上找,找到值为2,打印输出
注意:函数的作用域实在函数创建的时候决定的而不是调用的时候决定
并非根据调用嵌套形成(运行上下文)作用域链,而是根据函数创建嵌套行程作用域链,也就是函数的书写位置行程作用域链,因此成为词法作用域