温故而知新:JS 变量提拔与时候死区

最先实行剧本时,实行剧本的第一步是编译代码,然后再最先实行代码,如图

《温故而知新:JS 变量提拔与时候死区》

别的,在编译优化方面来讲,最最先时也并非悉数编译好剧本,而是当函数实行时,才会先编译,再实行剧本,如图

《温故而知新:JS 变量提拔与时候死区》

  • 编译阶段:阅历了词法剖析,语法剖析天生AST,以及代码天生。而且在此阶段,它只会扫描而且抽出环境中的声明变量,声明函数以便预备分派内存,一切的函数声明和变量声明都邑被添加到名为Lexical Environment的JavaScript内部数据结构内的内存中。因而,它们能够在源代码中现实声明之前运用。然则,Javascript只会存储函数声明和变量声明在内存,并不会存储他们的值
  • 实行阶段:给变量x赋值,起首讯问内存你这有变量x吗,假如有,则给变量x赋值,假如没有则建立变量x而且给它赋值。

变量提拔

以下图,左侧灰色块地区,是演示函数实行前的编译阶段,先抽出一切声明变量和声明函数,并进行内存分派。然后再最先实行代码,在实行第一行代码的时刻,如果变量a存在于内存中,则直接给变量a赋值。而实行到第二行时,变量b并没有在内存中,则会建立变量b并给它赋值。

《温故而知新:JS 变量提拔与时候死区》

Lexical enviroment是一种包括标识符变量映照的数据结构

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

简而言之,Lexical enviroment就是顺序实行过程当中变量和函数存在的处所。

let,const变量

console.log(a)
let a = 3;

输出

ReferenceError: a is not defined

所以let和const变量并不会被提拔吗?

这个答案会比较复杂。一切的声明(function, var, let, const and class)在JavaScript中都邑被提拔,但是var声明被undefined值初始化,然则letconst声明的值依然未被初始化。

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

假如JavaScript引擎依然没法在声明它们的行中找到let或许const的值,它将为它们分派undefined值或返回毛病值(在const的情况下会返回毛病值)。

《温故而知新:JS 变量提拔与时候死区》

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;

《温故而知新:JS 变量提拔与时候死区》

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;
  }
}

《温故而知新:JS 变量提拔与时候死区》

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

《温故而知新:JS 变量提拔与时候死区》

所以如今我们晓得在提拔过程当中我们的代码并没有被JavaScript引擎现实挪动。正确理解提拔机制将有助于防止因变量提拔而发生的任何将来毛病和杂沓。为了防止像未定义的变量或援用毛病一样能够发生的副作用,请一直尝试将变量声明在各自作用域的顶部,并一直尝试在声明变量时初始化变量。

Hoisting in Modern JavaScript — let, const, and var

    原文作者:Murphywuwu
    原文地址: https://segmentfault.com/a/1190000018033290
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞