【阅读】Javascript进阶阅读笔记 - 作用域篇

学习 · 01-28 · 53 人浏览

作用域,或者称之为“上下文”,是变量被承载的容器

词法环境

在ECMAScript规范中,定义了环境记录(Environment Record)的概念,它是抽象的,用来确定变量、函数等创建时所产生的词法环境,也就是所谓的作用域

Environment Record包含三类:

  1. Declarative Environment Record:val、let、const、function、class、module、catch
  2. Object Environment Record: Object、with
  3. Global 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,它的作用类似作用域链,具体的逻辑为:

  1. 确定当前的Environment Record
  2. 调用当前Environment Record的 HasThisbinding
  3. 返回true,则调用Environment Record的 GetThisBinding 并结束
  4. 否则将当前的Environment Record变为Environment Record的OuterEnv属性,继续步骤2
可以看出就是一个向上遍历的过程,哪一级的Environment Record有ThisBinding就使用它

接下来可以看下每种Environment Record的对于HasThisbinding的表现:

  1. Declarative Environment Record 始终返回false, 所以this都是指向Global Environment Record
  2. Function Environment Record 作为Declarative的子类, 会继承它的表现,但需要注意的是它还具有BindThisValue,即手动绑定的能力,所以只在默认的情况下和箭头函数一样,都会去寻找OuterEnv的this
  3. Global Environment Record 始终返回true,浏览器下是window,Node下为global
  4. Module 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所在的这一句代码没被执行前的任何位置,都无法去使用这个变量

Javascript进阶
Theme Jasmine by Kent Liao