从本质上理解JavaScript中的变量提高

JavaScript中奇怪的一点是你能够在变量和函数声明以前使用它们。就好像是变量声明和函数声明被提高了代码的顶部同样。javascript

sayHi() // Hi there!

function sayHi() {
    console.log('Hi there!')
}

name = 'John Doe'
console.log(name)   // John Doe
var name
复制代码

然而JavaScript并不会移动你的代码,因此JavaScript中“变量提高”并非真正意义上的“提高”。java

JavaScript是单线程语言,因此执行确定是按顺序执行。可是并非逐行的分析和执行,而是一段一段地分析执行,会先进行编译阶段而后才是执行阶段。数据结构

在编译阶段阶段,代码真正执行前的几毫秒,会检测到全部的变量和函数声明,全部这些函数和变量声明都被添加到名为Lexical Environment的JavaScript数据结构内的内存中。因此这些变量和函数能在它们真正被声明以前使用。函数

函数提高

sayHi() // Hi there!

function sayHi() {
    console.log('Hi there!')
}
复制代码

由于函数声明在编译阶段会被添加到词法环境(Lexical Environment)中,当JavaScript引擎遇到sayHi()函数时,它会从词法环境中找到这个函数并执行它。ui

lexicalEnvironment = {
  sayHi: < func > } 复制代码

var变量提高

console.log(name)   // 'undefined'
var name = 'John Doe'
console.log(name)   // John Doe
复制代码

上面的代码实际上分为两个部分:this

  • var name表示声明变量name
  • = 'John Doe'表示的是为变量name赋值为'John Doe'。
var name    // 声明变量
name = 'John Doe' // 赋值操做
复制代码

只有声明操做var name会被提高,而赋值这个操做并不会被提高,可是为何变量name的值会是undefined呢?spa

缘由是当JavaScript在编译阶段会找到var关键字声明的变量会添加到词法环境中,并初始化一个值undefined,在以后执行代码到赋值语句时,会把值赋值到这个变量。线程

// 编译阶段
lexicalEnvironment = {
  name: undefined
}

// 执行阶段
lexicalEnvironment = {
  name: 'John Doe'
}
复制代码

因此函数表达式也不会被“提高”。helloWorld是一个默认值是undefined的变量,而不是一个functioncode

helloWorld();  // TypeError: helloWorld is not a function

var helloWorld = function(){
  console.log('Hello World!');
}
复制代码

let & const提高

console.log(a)  // ReferenceError: a is not defined
let a = 3
复制代码

为何会报一个ReferenceError错误,难道letconst声明的变量没有被“提高”吗?xml

事实上全部的声明(function, var, let, const, class)都会被“提高”。可是只有使用var关键字声明的变量才会被初始化undefined值,而letconst声明的变量则不会被初始化值。

只有在执行阶段JavaScript引擎在遇到他们的词法绑定(赋值)时,他们才会被初始化。这意味着在JavaScript引擎在声明变量以前,没法访问该变量。这就是咱们所说的Temporal Dead Zone,即变量建立和初始化之间的时间跨度,它们没法访问。

若是JavaScript引擎在letconst变量被声明的地方还找不到值的话,就会被赋值为undefined或者返回一个错误(const的状况下)。

举例:

let a
console.log(a)  // undefined
a = 5
复制代码

在编译阶段,JavaScript引擎遇到变量a并将它存到词法环境中,但由于使用let关键字声明的,JavaScript引擎并不会为它初始化值,因此在编译阶段,此刻的词法环境像这样:

lexicalEnvironment = {
  a: <uninitialized> } 复制代码

若是咱们要在变量声明以前使用变量,JavaScript引擎会从词法环境中获取变量的值,可是变量此时仍是uninitialized状态,因此会返回一个错误ReferenceError

在执行阶段,当JavaScript引擎执行到变量被声明的时候,若是声明了变量并赋值,会更新词法环境中的值,若是只是声明了变量没有被赋值,那么JavaScript引擎会给变量赋值为undefined

tips: 咱们能够在letconst声明以前使用他们,只要代码不是在变量声明以前执行:

function foo() {
    console.log(name)
}

let name = 'John Doe'

foo()   // 'John Doe'
复制代码

Class提高

letconst同样,class在JavaScript中也是会被“提高”的,在被真正赋值以前都不会被初始化值, 一样受Temporal Dead Zone的影响。

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

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

let John = new Person('John', 25); 
console.log(peter) // Person { name: 'John', age: 25 }
复制代码
相关文章
相关标签/搜索