一文读懂var、let、const——从规范文档出发

前置知识

ES5 执行上下文和变量对象

可以阅读@冴羽大神的博客JavaScript 深入之变量对象

执行上下文(execution context):当 JavaScript 代码执行一段可执行代码(executable code)时,其所创建的对应的执行环境。可执行代码包括全局代码函数代码eval 代码,所以也分成全局执行上下文函数执行上下文eval 执行上下文

每个执行上下文中包含了三部分:

  • 变量对象(Variable Object, OV):与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。在函数执行上下文中又叫做激活对象(Activated Object, AO)。
  • 作用域链(Scope Chain):由多个执行上下文的变量对象构成的链表。每个作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限
  • this

ES6 执行上下文

ES6 规范文档可以了解到,ES6 的执行上下文中有三种组件:词法环境(LexicalEnvironment)、变量环境(VariableEnvironment)、this 绑定(ThisBinding)。这地方与 ES5 不太一样,为了区分 varlet/const 声明的变量存储位置。

推荐阅读@阅文前端团队-夏佳昊翻译的理解 Javascript 执行上下文和执行栈,再阅读这篇文章会更容易理解。

  1. this 绑定

在全局执行上下文中,this 的值指向全局对象,在浏览器中,this 的值指向 window 对象,node.js 中指向 global 对象。

在函数执行上下文中,this 的值取决于函数的调用方式。如果它被一个对象引用调用,那么 this 的值被设置为该对象,否则 this 的值被设置为全局对象或 undefined(严格模式下)

  1. 词法环境

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.

词法环境是一种规范类型,基于词法嵌套结构来定义标识符与特定变量和函数的关联关系。词法环境由两部分组成:

  • 环境记录(environment record):存储变量和函数声明的实际位置(变量对象)。环境记录也分为两类:

    • 声明环境记录:存储变量、函数和参数。对应 Function Environment
    • 对象环境记录:用于定义在全局执行上下文中出现的变量和函数的关联。对应 Global Environment
  • 对外部环境的引用:表示它可以访问其外部词法环境,可以为 null。(作用域链)

用伪代码的形式表示就如下:

ExecutionContext = {
  ThisBinding: < Global Object >
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object" / "Declarative",
      // Identifier bindings go here
    }
    outer: <null>
  }
}
  1. 变量环境

Identifies the Lexical Environment whose EnvironmentRecord holds bindings created by VariableStatements within this execution context.

变量环境是一种特殊的词法环境,它的环境记录维护了执行上下文中的 VariableStatements 创建的绑定。

在 ES6 中,LexicalEnvironment 组件和 VariableEnvironment 组件的区别在于前者用于存储函数声明和变量( letconst )绑定,而后者仅用于存储变量( var )绑定。

var 关键字

var 关键字做了什么?

  1. 使用 var 关键字声明变量时,如var a = 'a'当进入该执行上下文时,会在其执行上下文中的变量对象中声明该变量a: undefined,即声明提升(hoist),等解析到赋值语句时再更新这个变量的值

  2. 假如执行上下文是函数执行上下文,则这个变量是局部变量,如果是全局执行上下文,则其声明的是一个全局变量。

  3. 访问这些变量时,会严格按照作用域链访问

比如下面的例子,c 是局部变量,当 baz 的执行上下文弹出后,在全局执行上下文的变量对象中并没有声明 c,且作用域链未指向 baz 的变量对象,所以报错c is not defined

function baz() {
  console.log(c);
  var c = 3;
}
baz(); // undefined
console.log(c); // c is not defined

不使用 var 声明变量

  1. 如果未使用 var 关键字,则隐性声明一个全局对象属性,如b = 'b',则是在全局对象上增加一个属性b,其值为'b'

  2. 全局执行上下文的变量对象寄宿在全局变量对象上,所以不使用 var 声明的对象在所有执行上下文中都可以访问

  3. 不会声明提升,遇到声明赋值语句时,直接在全局对象上添加该属性

比如下面的例子,未使用隐性声明变量 a ,不存在变量声明提升,所以执行到 console.log(a) 语句时,foo 函数执行上下文中的激活对象中没有声明变量 a,所以报错a is not defined

function foo() {
  console.log(a);
  a = 1;
}
foo(); // a is not defined

全局变量对象与全局对象

全局对象在不同实现中表示也不同,比如浏览器端就是 windownode.js 中就是 global,全局对象是全局变量对象的宿主,所以在全局变量对象中可以访问全局对象中的属性

全局变量对象中的属性无法通过 delete 删除,而全局对象的属性可以通过 delete 删除。其实质是全局变量上的属性描述符 configurable 被设置为了 false,所以无法删除、

在这里插入图片描述

let 和 const

ECMA-262 13.3.1 Let and Const Declarations

let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

上面的定义可以得到三个结论:

  • letconst 声明的是一个执行上下文中的块级词法环境的变量
  • letconst 当进入词法环境中时就会创建这些变量,即存在声明提升,但在声明语句前不可使用该变量,即暂时性死区(temporal dead zone, TDZ)
  • let 声明变量时未初始化值,则默认为 undefined

其他结论:

  • letconst 不可重复声明
  • const 必须初始化值,且该变量(常量)具有不可变性。但其不可变指的是基础类型不可变,但引用类型的变量声明只是栈区中指向堆区的指针,所以可以修改引用类型在堆区的实际数据。

示例

let a = "a";
function foo(f) {
    
    
  const b = "b";
  debugger;
  let d = "d";
}
foo("f");
var g = "g";

在进入 debugger 断点处时,执行全局执行上下文与函数执行上下文的可以表示为如下:

GlobalExecutionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: 'a',
      foo: < function >
    }
    outer: <null>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      g: undefined,
    }
    outer: <null>
  }
}

FunctionExecutionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      Arguments: {
        f: 'f',
        length: 1
      },
      b: 'b',
      d: < uninitialized >
    },
    outer: <GlobalLexicalEnvironment>
  }
}

在 chrome 控制台运行,断点如下:

在这里插入图片描述

参考

猜你喜欢

转载自blog.csdn.net/sinat_36521655/article/details/107960380
今日推荐