资讯专栏INFORMATION COLUMN

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

codecraft / 1986人阅读

摘要:这意味着引擎在源代码中声明它的位置计算其值之前,你无法访问该变量。因此它们同样会受到时间死区的影响。正确理解提升机制将有助于避免因变量提升而产生的任何未来错误和混乱。

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

另外,在编译优化方面来说,最开始时也并不是全部编译好脚本,而是当函数执行时,才会先编译,再执行脚本,如图

编译阶段:经历了词法分析,语法分析生成AST,以及代码生成。并且在此阶段,它只会扫描并且抽出环境中的声明变量,声明函数以便准备分配内存,所有的函数声明和变量声明都会被添加到名为Lexical Environment的JavaScript内部数据结构内的内存中。因此,它们可以在源代码中实际声明之前使用。但是,Javascript只会存储函数声明和变量声明在内存,并不会存储他们的值

执行阶段:给变量x赋值,首先询问内存你这有变量x吗,如果有,则给变量x赋值,如果没有则创建变量x并且给它赋值。

变量提升

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

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

LexicalEnviroment = {
  Identifier: ,
  Indentifier: 
}

简而言之,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的情况下会返回错误值)。

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

现在如果我们尝试在声明它之前访问该变量,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;

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

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

lexicalEnvironment = {
  Person: 
}
提升Class Expressions
let peter = new Person("Peter", 25);
console.log(peter);
let Person = class {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

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

所以现在我们知道在提升过程中我们的代码并没有被JavaScript引擎实际移动。正确理解提升机制将有助于避免因变量提升而产生的任何未来错误和混乱。为了避免像未定义的变量或引用错误一样可能产生的副作用,请始终尝试将变量声明在各自作用域的顶部,并始终尝试在声明变量时初始化变量。

Hoisting in Modern JavaScript — let, const, and var

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108879.html

相关文章

  • 理解ES6中的暂时死区(TDZ)

    摘要:以英文名词来说明,是时间的暂时的意义,则是死区,意指电波达不到的区域。所以可以翻为时间上暂时的无法达到的区域,简称为时间死区或暂时死区。以声明的变量或常量,必需是经过对声明的赋值语句的求值后,才算初始化完成,创建时并不算初始化。 Temporal Dead Zone(TDZ)是ES6(ES2015)中对作用域新的专用语义。TDZ名词并没有明确地写在ES6的标准文件中,一开始是出现在ES...

    Mike617 评论0 收藏0
  • 深入理解let和var的区别(暂时性死区)!!!

    摘要:会出现这样的情况是因为拥有暂时性死区。规定暂时性死区和语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。 首先我们应该知道js引擎在读取js代码时会进行两个步骤: 第一个步骤是解释。 第二个步骤是执行。 所谓解释就是会先通篇扫描所有的Js代码,然后把所有声明提升到顶端,第二步是执行,执行就是操作一类的。 我们先来看个简单的变量提升...

    tanglijun 评论0 收藏0
  • JS变量生命周期:为什么 let 没有被提升

    摘要:请注意,就变量生命周期而言,声明阶段与变量声明是不同的概念。提升在生命周期中无效的原因如上所述,提升是变量在作用域顶部的耦合声明和初始化阶段。然而,生命周期分离声明和初始化阶段。解耦消除了的提升期限。 为了保证的可读性,本文采用意译而非直译。 提升是将变量或函数定义移动到作用域头部的过程,通常是 var 声明的变量和函数声明function fun() {...}。 当 ES6 引入l...

    hoohack 评论0 收藏0
  • JS变量生命周期:为什么 let 没有被提升

    摘要:请注意,就变量生命周期而言,声明阶段与变量声明是不同的概念。提升在生命周期中无效的原因如上所述,提升是变量在作用域顶部的耦合声明和初始化阶段。然而,生命周期分离声明和初始化阶段。解耦消除了的提升期限。 为了保证的可读性,本文采用意译而非直译。 提升是将变量或函数定义移动到作用域头部的过程,通常是 var 声明的变量和函数声明function fun() {...}。 当 ES6 引入l...

    Steven 评论0 收藏0
  • JavaScript中函数纪要(一)

    摘要:中函数是一等公民,所有的函数实际上是一个对象,与其他引用类型一样拥有着属性和方法,也可以被外界或者自身调用,也可以像传递参数一样将函数传递给另一个函数。中函数没有重载的概念,当定义两个同名函数的时候,前一个函数会被覆盖掉,举个栗子。 JavaScript中函数是一等公民,所有的函数实际上是一个Function对象,与其他引用类型一样拥有着属性和方法,也可以被外界或者自身调用,也可以像传...

    plus2047 评论0 收藏0

发表评论

0条评论

codecraft

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<