先简单说下执行栈是什么:
js执行代码时会创造一个执行上下文(里面包括执行代码所需要的信息),比如下面代码
function foo(){ let a=1 return function b(){ console.log(a) } b() } foo()
调用函数foo时先创建了函数foo的执行上下文,执行到return 的时候又在执行栈里添加了新的函数b的执行上下文,等函数b执行完了之后就把函数b的执行上下文从执行栈里踢出去,再然后foo的执行上下文也踢出去
再详细说一下执行上下文
在es5里它包含了下面三个内容
- lexical environment:词法环境,当获取变量时使用。
- variable environment:变量环境,当声明变量时使用。
- this value:this 值。
但在es6之后是这个样子的
- lexical environment:词法环境(环境记录+外部词法引用),当获取变量或者 this 值时使用。
- variable environment:变量环境,当声明变量时使用。
- code evaluation state:用于恢复代码执行位置。
- Function:执行的任务是函数时使用,表示正在被执行的函数。
- ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。
- Realm:使用的基础库和内置对象实例。
- Generator:仅生成器上下文有这个属性,表示当前生成器。
词法环境会通过内部的环境记录来存储标识符与实际变量的映射关系,一个外部词法引用用于查找非当前作用域的变量时进行逐级溯源查找,以及绑定当前作用域的this指针
再聊一聊let/const/var
var 会将变量提升到当前函数作用域最顶端,let和const则不会,而且let碰到以下语句
- for;
- if;
- switch;
- try/catch/finally
会生成块级作用域
所以:
for(var i=0;i<5;i++){ setTimeout(()=>{console.log(i)},1000) // i打印出来的都是5 } for(let i=0;i<5;i++){ setTimeout(()=>{console.log(i)},1000) // i打印出来的是0,1,2,3,4 }
并且
var、let、const class和function 在执行上下文的创建阶段时,js会在创建阶段执行上下文就将声明的变量、函数和类都创建,但是var 声明的变量会被初始化为undefined,而 let / const / class 声明的变量不会被初始化 状态为 uninitialized ,并且禁止访问,function 关键字的声明会直接将函数体赋值给对应的函数名。而且除了var之外,后面几个关键字声明的标识符映射是存放在词法环境里,而var声明的标识符映射是存放在变量环境里
所以: console.log(c); let c =1; //Uncaught ReferenceError: Cannot access 'c' before initialization //但是var console.log(d) //undefiend var d=1;
再谈谈闭包
根据古典闭包定义( closure )包含:
- 环境部分(环境和标识符列表)
- 表达式部分
我们在对应到js里面
- 环境(函数的词法环境)
- 标识符列表( 函数中用到的未声明的变量 )
- 表达式(函数体)
其实闭包就是一个带有词法环境的函数