從本質上理解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 }
複製代碼
相關文章
相關標籤/搜索