作用域,或者称之为“上下文”,是变量被承载的容器
词法环境
在ECMAScript规范中,定义了环境记录(Environment Record)的概念,它是抽象的,用来确定变量、函数等创建时所产生的词法环境,也就是所谓的作用域
Environment Record包含三类:
Declarative Environment Record
:val、let、const、function、class、module、catchObject Environment Record
: Object、withGlobal Environment Record
:globalThis、global其中Declarative Environment Record下的 function 对应了其子级的Function Environment Record,module对应Module Environment Record
Environment Record具有上下级的关系,每个Environment Record都有一个OuterEnv属性,指向了上级作用域,所以对应的作用域可以形成一个树结构,举个例子
var foo = 1; // global Env
function onload() {
var bar = 2; // Function Env1
function onload2(){
var baz = 3 // Function Env2
console.log(baz+bar+foo)
}
onload2()
}
onload()
当console
执行时,会先在Function Env2
中找到baz变量,接着由于bar不存在,会去到Function Env2对应OuterEnv的 Function Env1中寻找,以此类型,最后输出6, 这个寻找过程就是作用域链
注意不同类型的Environment Record的OuterEnv是受限的,比如Global Environment Record的OuterEnv必定是null,Module Environment Record的OuterEnv必定是Global Environment Record、
This
在有了前者词法环境的概念后, 那么this
的工作就很好理解了。
在所有的Environment Record中都有HasThisBinding
的函数,用于确定this,它的作用类似作用域链,具体的逻辑为:
- 确定当前的Environment Record
- 调用当前Environment Record的 HasThisbinding
- 返回true,则调用Environment Record的
GetThisBinding
并结束 - 否则将当前的Environment Record变为Environment Record的OuterEnv属性,继续步骤2
可以看出就是一个向上遍历的过程,哪一级的Environment Record有ThisBinding
就使用它
接下来可以看下每种Environment Record的对于HasThisbinding
的表现:
Declarative Environment Record
始终返回false, 所以this都是指向Global Environment RecordFunction Environment Record
作为Declarative的子类, 会继承它的表现,但需要注意的是它还具有BindThisValue
,即手动绑定的能力,所以只在默认的情况下和箭头函数一样,都会去寻找OuterEnv
的thisGlobal Environment Record
始终返回true,浏览器下是window,Node下为globalModule Environment Record
作为Declarative的子类,但不同的是返回true,因为它的GetThisBinding会始终返回undefined
, 所以在ES Module中this始终是undefiend非ES Module下取决于是否为严格模式,非严格模式下this为 window/global
变量提升和TDZ
Js中的var声明存在变量提升的这一问题,即使它是在后来的语句才声明,但仍然可以被访问并输出undefined.
并且不管是大括号声明,还是for循环、try Catch都无法困住它,为此let、const就出现了
let、const在声明被执行前是无法被访问的,这在社区有一种约定的说法叫TDZ(暂行性死区),即let/const所在的这一句代码没被执行前的任何位置,都无法去使用这个变量