温故而知新:JS 变量提高与时间死区

开始执行脚本时,执行脚本的第一步是编译代码,而后再开始执行代码,如图javascript

clipboard.png

另外,在编译优化方面来讲,最开始时也并非所有编译好脚本,而是当函数执行时,才会先编译,再执行脚本,如图java

clipboard.png

  • 编译阶段:经历了词法分析,语法分析生成AST,以及代码生成。而且在此阶段,它只会扫描而且抽出环境中的声明变量,声明函数以便准备分配内存,全部的函数声明和变量声明都会被添加到名为Lexical Environment的JavaScript内部数据结构内的内存中。所以,它们能够在源代码中实际声明以前使用。可是,Javascript只会存储函数声明和变量声明在内存,并不会存储他们的值
  • 执行阶段:给变量x赋值,首先询问内存你这有变量x吗,若是有,则给变量x赋值,若是没有则建立变量x而且给它赋值。

变量提高

以下图,左边灰色块区域,是演示函数执行前的编译阶段,先抽出全部声明变量和声明函数,并进行内存分配。而后再开始执行代码,在执行第一行代码的时候,如果变量a存在于内存中,则直接给变量a赋值。而执行到第二行时,变量b并无在内存中,则会建立变量b并给它赋值。数据结构

clipboard.png

Lexical enviroment是一种包含标识符变量映射的数据结构app

LexicalEnviroment = {
  Identifier: <value>,
  Indentifier: <function object>
}

简而言之,Lexical enviroment就是程序执行过程当中变量和函数存在的地方。函数

let,const变量

console.log(a)
let a = 3;

输出优化

ReferenceError: a is not defined

因此let和const变量并不会被提高吗?this

这个答案会比较复杂。全部的声明(function, var, let, const and class)在JavaScript中都会被提高,然而var声明被undefined值初始化,可是letconst声明的值仍然未被初始化。spa

它们仅仅只在Javascript引擎运行期间它们的词法绑定被执行在才会被初始化。这意味着引擎在源代码中声明它的位置计算其值以前,你没法访问该变量。这就是咱们所说的时间死区,即变量建立和初始化之间的时间,咱们没法访问该变量。code

若是JavaScript引擎仍然没法在声明它们的行中找到let或者const的值,它将为它们分配undefined值或返回错误值(在const的状况下会返回错误值)。blog

clipboard.png

6a9a50532bf60f5fac6b3c.png](evernotecid://F2BCA3B5-CC5A-4EB3-BD61-DD865800F342/appyinxiangcom/10369121/ENResource/p1163)

let a;
console.log(a); // outputs undefined
a = 5;

在编译阶段,JavaScript引擎遇到变量a并将它存储在lexical enviroment,可是由于它是一个let变量,因此引擎不会为它初始化任何值。因此,在编译阶段,lexical enviroment看起来像下面这样。

// 编译阶段
lexicalEnvironment = {
  a: <uninitialized>
}

如今若是咱们尝试在声明它以前访问该变量,JavaScript引擎将会尝试从词法环境中拿到这个变量的值,由于这个变量未被初始化,它将抛出一个引用错误。

在执行期间,当引擎到达了变量声明的行,它将试图执行它的绑定,由于该变量没有与之关联的值,所以它将为其赋值为unedfined

// 执行阶段
lexicalEnviroment = {
  a: undefined
}

以后,undefined将会被打印到控制台,而后将值5赋值给变量a,lexical enviroment中变量a的值也会从undefined更新为5

functionn foo() {
  console.log(a)
}

let a = 20;

foo();
function foo() {
  console.log(a): // ReferenceError: a is not defined
}
foo();
let a = 20;

clipboard.png

Class Declaration

就像letconst声明同样,class在JavaScript中也会被提高,而且和let,const同样,知道执行以前,它们都会保持uninitialized。所以它们一样会受到Temporal Deal Zone(时间死区)的影响。例如

let peter = new Person('Peter', 25); // ReferenceError: Person is not defined

console.log(peter);

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

所以要访问class,必须先声明它

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

let peter = new Person('Peter', 25); 
console.log(peter);
// Person { name: 'Peter', age: 25 }

因此在编译阶段,上面代码的lexical environment(词法环境)将以下所示:

lexicalEnvironment = {
  Person: <uninitialized>
}

当引擎执行class声明时,它将使用值初始化类。

lexicalEnvironment = {
  Person: <Person object>
}

提高Class Expressions

let peter = new Person('Peter', 25);
console.log(peter);
let Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

clipboard.png

let peter = new Person('Peter', 25); 
console.log(peter);
var Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

clipboard.png

因此如今咱们知道在提高过程当中咱们的代码并无被JavaScript引擎实际移动。正确理解提高机制将有助于避免因变量提高而产生的任何将来错误和混乱。为了不像未定义的变量或引用错误同样可能产生的反作用,请始终尝试将变量声明在各自做用域的顶部,并始终尝试在声明变量时初始化变量。

Hoisting in Modern JavaScript — let, const, and var

相关文章
相关标签/搜索