'JS變量生命週期:為什麼 let 沒有被提升'

JavaScript GitHub 啟迪雲Tuscloud 2019-07-21
"

譯者:前端小智

原文:https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/

為了保證的可讀性,本文采用意譯而非直譯。

提升是將變量或函數定義移動到作用域頭部的過程,通常是 var 聲明的變量和函數聲明 functionfun(){...}。

當 ES6 引入 let(以及與 let類似聲明的 const和 class)聲明時,許多開發人員都使用提升定義來描述如何訪問變量。但是在對這個問題進行了更多的探討之後,令我驚訝的是提升並不是描述 let變量的初始化和可用性的正確術語。

ES6 為 let提供了一個不同的和改進的機制。它要求更嚴格的變量聲明,在定義之前不能使用,從而提高代碼質量。

1. 容易出錯的 var 提升

有時候我們會在zuo內作用域內看到一個奇怪的變量 varvarname和函數函數function funName(){...} 聲明:

// var hoisting
num
;

// => undefined
var
num
;
num
=

10
;
num
;

// => 10
// function hoisting
getPi
;

// => function getPi() {...}
getPi
();

// => 3.14
function
getPi
()

{

return

3.14
;
}

變量 num在聲明 varnum之前被訪問,因此它被賦值為 undefined。 fucntion getPi(){…}在文件末尾定義。但是,可以在聲明 getPi()之前調用該函數,因為它被提升到作用域的頂部。

事實證明,先使用然後聲明變量或函數的可能性會造成混淆。假設您滾動一個大文件,突然看到一個未聲明的變量,它到底是如何出現在這裡的,以及它在哪裡定義的?

當然,一個熟練的JavaScript開發人員不會這樣編寫代碼。但是在成千上萬的JavaScript中,GitHub repos是很有可能處理這樣的代碼的。

即使查看上面給出的代碼示例,也很難理解代碼中的聲明流。

當然,首先要聲明再使用。 let 鼓勵咱們使用這種方法處理變量。

2. 理解背後原理:變量生命週期

當引擎處理變量時,它們的生命週期由以下階段組成:


  1. 聲明階段(Declaration phase)是在作用域中註冊一個變量。
  2. 初始化階段(Initialization phase)是分配內存併為作用域中的變量創建綁定。在此步驟中,變量將使用 undefined自動初始化。
  3. 賦值階段(Assignment phase)是為初始化的變量賦值。

變量在通過聲明階段時尚未初始化狀態,但未達到初始化狀態。

"

譯者:前端小智

原文:https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/

為了保證的可讀性,本文采用意譯而非直譯。

提升是將變量或函數定義移動到作用域頭部的過程,通常是 var 聲明的變量和函數聲明 functionfun(){...}。

當 ES6 引入 let(以及與 let類似聲明的 const和 class)聲明時,許多開發人員都使用提升定義來描述如何訪問變量。但是在對這個問題進行了更多的探討之後,令我驚訝的是提升並不是描述 let變量的初始化和可用性的正確術語。

ES6 為 let提供了一個不同的和改進的機制。它要求更嚴格的變量聲明,在定義之前不能使用,從而提高代碼質量。

1. 容易出錯的 var 提升

有時候我們會在zuo內作用域內看到一個奇怪的變量 varvarname和函數函數function funName(){...} 聲明:

// var hoisting
num
;

// => undefined
var
num
;
num
=

10
;
num
;

// => 10
// function hoisting
getPi
;

// => function getPi() {...}
getPi
();

// => 3.14
function
getPi
()

{

return

3.14
;
}

變量 num在聲明 varnum之前被訪問,因此它被賦值為 undefined。 fucntion getPi(){…}在文件末尾定義。但是,可以在聲明 getPi()之前調用該函數,因為它被提升到作用域的頂部。

事實證明,先使用然後聲明變量或函數的可能性會造成混淆。假設您滾動一個大文件,突然看到一個未聲明的變量,它到底是如何出現在這裡的,以及它在哪裡定義的?

當然,一個熟練的JavaScript開發人員不會這樣編寫代碼。但是在成千上萬的JavaScript中,GitHub repos是很有可能處理這樣的代碼的。

即使查看上面給出的代碼示例,也很難理解代碼中的聲明流。

當然,首先要聲明再使用。 let 鼓勵咱們使用這種方法處理變量。

2. 理解背後原理:變量生命週期

當引擎處理變量時,它們的生命週期由以下階段組成:


  1. 聲明階段(Declaration phase)是在作用域中註冊一個變量。
  2. 初始化階段(Initialization phase)是分配內存併為作用域中的變量創建綁定。在此步驟中,變量將使用 undefined自動初始化。
  3. 賦值階段(Assignment phase)是為初始化的變量賦值。

變量在通過聲明階段時尚未初始化狀態,但未達到初始化狀態。

JS變量生命週期:為什麼 let 沒有被提升

請注意,就變量生命週期而言,聲明階段與變量聲明是不同的概念。簡而言之,JS引擎在3個階段處理變量聲明:聲明階段,初始化階段和賦值階段。

3.var 變量的生命週期

熟悉生命週期階段之後,讓我們使用它們來描述JS引擎如何處理 var變量。

"

譯者:前端小智

原文:https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/

為了保證的可讀性,本文采用意譯而非直譯。

提升是將變量或函數定義移動到作用域頭部的過程,通常是 var 聲明的變量和函數聲明 functionfun(){...}。

當 ES6 引入 let(以及與 let類似聲明的 const和 class)聲明時,許多開發人員都使用提升定義來描述如何訪問變量。但是在對這個問題進行了更多的探討之後,令我驚訝的是提升並不是描述 let變量的初始化和可用性的正確術語。

ES6 為 let提供了一個不同的和改進的機制。它要求更嚴格的變量聲明,在定義之前不能使用,從而提高代碼質量。

1. 容易出錯的 var 提升

有時候我們會在zuo內作用域內看到一個奇怪的變量 varvarname和函數函數function funName(){...} 聲明:

// var hoisting
num
;

// => undefined
var
num
;
num
=

10
;
num
;

// => 10
// function hoisting
getPi
;

// => function getPi() {...}
getPi
();

// => 3.14
function
getPi
()

{

return

3.14
;
}

變量 num在聲明 varnum之前被訪問,因此它被賦值為 undefined。 fucntion getPi(){…}在文件末尾定義。但是,可以在聲明 getPi()之前調用該函數,因為它被提升到作用域的頂部。

事實證明,先使用然後聲明變量或函數的可能性會造成混淆。假設您滾動一個大文件,突然看到一個未聲明的變量,它到底是如何出現在這裡的,以及它在哪裡定義的?

當然,一個熟練的JavaScript開發人員不會這樣編寫代碼。但是在成千上萬的JavaScript中,GitHub repos是很有可能處理這樣的代碼的。

即使查看上面給出的代碼示例,也很難理解代碼中的聲明流。

當然,首先要聲明再使用。 let 鼓勵咱們使用這種方法處理變量。

2. 理解背後原理:變量生命週期

當引擎處理變量時,它們的生命週期由以下階段組成:


  1. 聲明階段(Declaration phase)是在作用域中註冊一個變量。
  2. 初始化階段(Initialization phase)是分配內存併為作用域中的變量創建綁定。在此步驟中,變量將使用 undefined自動初始化。
  3. 賦值階段(Assignment phase)是為初始化的變量賦值。

變量在通過聲明階段時尚未初始化狀態,但未達到初始化狀態。

JS變量生命週期:為什麼 let 沒有被提升

請注意,就變量生命週期而言,聲明階段與變量聲明是不同的概念。簡而言之,JS引擎在3個階段處理變量聲明:聲明階段,初始化階段和賦值階段。

3.var 變量的生命週期

熟悉生命週期階段之後,讓我們使用它們來描述JS引擎如何處理 var變量。

JS變量生命週期:為什麼 let 沒有被提升

假設JS遇到一個函數作用域,其中包含 var變量語句。變量在執行任何語句之前通過聲明階段,並立即通過作用域開始處的初始化階段(步驟1)。函數作用域中 var變量語句的位置不影響聲明和初始化階段。

在聲明和初始化之後,但在賦值階段之前,變量具有 undefined 的值,並且已經可以使用。

在賦值階段 variable='value' 時,變量接收它的初值(步驟2)。

嚴格意義的提升是指在函數作用域的開始處聲明並初始化一個變量。聲明階段和初始化階段之間沒有差別。

讓我們來研究一個例子。下面的代碼創建了一個包含 var語句的函數作用域

function
multiplyByTen
(
number
)

{
console
.
log
(
ten
);

// => undefined

var
ten
;
ten
=

10
;
console
.
log
(
ten
);

// => 10

return
number
*
ten
;
}
multiplyByTen
(
4
);

// => 40

開始執行 multipleByTen(4)並進入函數作用域時,變量 ten在第一個語句之前通過聲明和初始化步驟。因此,當調用 console.log(ten)時,打印 undefined。語句 ten=10指定一個初值。賦值之後, console.log(ten) 將正確地打印 10。

4. 函數聲明生命週期

在函數聲明語句 functionfunName(){...}的情況下,它比變量聲明生命週期更簡單。

"

譯者:前端小智

原文:https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/

為了保證的可讀性,本文采用意譯而非直譯。

提升是將變量或函數定義移動到作用域頭部的過程,通常是 var 聲明的變量和函數聲明 functionfun(){...}。

當 ES6 引入 let(以及與 let類似聲明的 const和 class)聲明時,許多開發人員都使用提升定義來描述如何訪問變量。但是在對這個問題進行了更多的探討之後,令我驚訝的是提升並不是描述 let變量的初始化和可用性的正確術語。

ES6 為 let提供了一個不同的和改進的機制。它要求更嚴格的變量聲明,在定義之前不能使用,從而提高代碼質量。

1. 容易出錯的 var 提升

有時候我們會在zuo內作用域內看到一個奇怪的變量 varvarname和函數函數function funName(){...} 聲明:

// var hoisting
num
;

// => undefined
var
num
;
num
=

10
;
num
;

// => 10
// function hoisting
getPi
;

// => function getPi() {...}
getPi
();

// => 3.14
function
getPi
()

{

return

3.14
;
}

變量 num在聲明 varnum之前被訪問,因此它被賦值為 undefined。 fucntion getPi(){…}在文件末尾定義。但是,可以在聲明 getPi()之前調用該函數,因為它被提升到作用域的頂部。

事實證明,先使用然後聲明變量或函數的可能性會造成混淆。假設您滾動一個大文件,突然看到一個未聲明的變量,它到底是如何出現在這裡的,以及它在哪裡定義的?

當然,一個熟練的JavaScript開發人員不會這樣編寫代碼。但是在成千上萬的JavaScript中,GitHub repos是很有可能處理這樣的代碼的。

即使查看上面給出的代碼示例,也很難理解代碼中的聲明流。

當然,首先要聲明再使用。 let 鼓勵咱們使用這種方法處理變量。

2. 理解背後原理:變量生命週期

當引擎處理變量時,它們的生命週期由以下階段組成:


  1. 聲明階段(Declaration phase)是在作用域中註冊一個變量。
  2. 初始化階段(Initialization phase)是分配內存併為作用域中的變量創建綁定。在此步驟中,變量將使用 undefined自動初始化。
  3. 賦值階段(Assignment phase)是為初始化的變量賦值。

變量在通過聲明階段時尚未初始化狀態,但未達到初始化狀態。

JS變量生命週期:為什麼 let 沒有被提升

請注意,就變量生命週期而言,聲明階段與變量聲明是不同的概念。簡而言之,JS引擎在3個階段處理變量聲明:聲明階段,初始化階段和賦值階段。

3.var 變量的生命週期

熟悉生命週期階段之後,讓我們使用它們來描述JS引擎如何處理 var變量。

JS變量生命週期:為什麼 let 沒有被提升

假設JS遇到一個函數作用域,其中包含 var變量語句。變量在執行任何語句之前通過聲明階段,並立即通過作用域開始處的初始化階段(步驟1)。函數作用域中 var變量語句的位置不影響聲明和初始化階段。

在聲明和初始化之後,但在賦值階段之前,變量具有 undefined 的值,並且已經可以使用。

在賦值階段 variable='value' 時,變量接收它的初值(步驟2)。

嚴格意義的提升是指在函數作用域的開始處聲明並初始化一個變量。聲明階段和初始化階段之間沒有差別。

讓我們來研究一個例子。下面的代碼創建了一個包含 var語句的函數作用域

function
multiplyByTen
(
number
)

{
console
.
log
(
ten
);

// => undefined

var
ten
;
ten
=

10
;
console
.
log
(
ten
);

// => 10

return
number
*
ten
;
}
multiplyByTen
(
4
);

// => 40

開始執行 multipleByTen(4)並進入函數作用域時,變量 ten在第一個語句之前通過聲明和初始化步驟。因此,當調用 console.log(ten)時,打印 undefined。語句 ten=10指定一個初值。賦值之後, console.log(ten) 將正確地打印 10。

4. 函數聲明生命週期

在函數聲明語句 functionfunName(){...}的情況下,它比變量聲明生命週期更簡單。

JS變量生命週期:為什麼 let 沒有被提升

聲明、初始化和賦值階段同時發生在封閉函數作用域的開頭(只有一步)。可以在作用域的任何位置調用 funName(),而不依賴於聲明語句的位置(甚至可以在末尾調用)。

下面的代碼示例演示了函數提升:

function
sumArray
(
array
)

{

return
array
.
reduce
(
sum
);

function
sum
(
a
,
b
)

{

return
a
+
b
;

}
}
sumArray
([
5
,

10
,

8
]);

// => 23

當執行 sumArray([5,10,8])時,它進入 sumArray函數作用域。在這個作用域內,在任何語句執行之前, sum都會通過所有三個階段:聲明、初始化和賦值。這樣, array.reduce(sum)甚至可以在它的聲明語句 sum(a,b){…}之前使用 sum。

5. let 變量的生命週期

let 變量的處理方式與 var不同,主要區別在於聲明和初始化階段是分開的

"

譯者:前端小智

原文:https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/

為了保證的可讀性,本文采用意譯而非直譯。

提升是將變量或函數定義移動到作用域頭部的過程,通常是 var 聲明的變量和函數聲明 functionfun(){...}。

當 ES6 引入 let(以及與 let類似聲明的 const和 class)聲明時,許多開發人員都使用提升定義來描述如何訪問變量。但是在對這個問題進行了更多的探討之後,令我驚訝的是提升並不是描述 let變量的初始化和可用性的正確術語。

ES6 為 let提供了一個不同的和改進的機制。它要求更嚴格的變量聲明,在定義之前不能使用,從而提高代碼質量。

1. 容易出錯的 var 提升

有時候我們會在zuo內作用域內看到一個奇怪的變量 varvarname和函數函數function funName(){...} 聲明:

// var hoisting
num
;

// => undefined
var
num
;
num
=

10
;
num
;

// => 10
// function hoisting
getPi
;

// => function getPi() {...}
getPi
();

// => 3.14
function
getPi
()

{

return

3.14
;
}

變量 num在聲明 varnum之前被訪問,因此它被賦值為 undefined。 fucntion getPi(){…}在文件末尾定義。但是,可以在聲明 getPi()之前調用該函數,因為它被提升到作用域的頂部。

事實證明,先使用然後聲明變量或函數的可能性會造成混淆。假設您滾動一個大文件,突然看到一個未聲明的變量,它到底是如何出現在這裡的,以及它在哪裡定義的?

當然,一個熟練的JavaScript開發人員不會這樣編寫代碼。但是在成千上萬的JavaScript中,GitHub repos是很有可能處理這樣的代碼的。

即使查看上面給出的代碼示例,也很難理解代碼中的聲明流。

當然,首先要聲明再使用。 let 鼓勵咱們使用這種方法處理變量。

2. 理解背後原理:變量生命週期

當引擎處理變量時,它們的生命週期由以下階段組成:


  1. 聲明階段(Declaration phase)是在作用域中註冊一個變量。
  2. 初始化階段(Initialization phase)是分配內存併為作用域中的變量創建綁定。在此步驟中,變量將使用 undefined自動初始化。
  3. 賦值階段(Assignment phase)是為初始化的變量賦值。

變量在通過聲明階段時尚未初始化狀態,但未達到初始化狀態。

JS變量生命週期:為什麼 let 沒有被提升

請注意,就變量生命週期而言,聲明階段與變量聲明是不同的概念。簡而言之,JS引擎在3個階段處理變量聲明:聲明階段,初始化階段和賦值階段。

3.var 變量的生命週期

熟悉生命週期階段之後,讓我們使用它們來描述JS引擎如何處理 var變量。

JS變量生命週期:為什麼 let 沒有被提升

假設JS遇到一個函數作用域,其中包含 var變量語句。變量在執行任何語句之前通過聲明階段,並立即通過作用域開始處的初始化階段(步驟1)。函數作用域中 var變量語句的位置不影響聲明和初始化階段。

在聲明和初始化之後,但在賦值階段之前,變量具有 undefined 的值,並且已經可以使用。

在賦值階段 variable='value' 時,變量接收它的初值(步驟2)。

嚴格意義的提升是指在函數作用域的開始處聲明並初始化一個變量。聲明階段和初始化階段之間沒有差別。

讓我們來研究一個例子。下面的代碼創建了一個包含 var語句的函數作用域

function
multiplyByTen
(
number
)

{
console
.
log
(
ten
);

// => undefined

var
ten
;
ten
=

10
;
console
.
log
(
ten
);

// => 10

return
number
*
ten
;
}
multiplyByTen
(
4
);

// => 40

開始執行 multipleByTen(4)並進入函數作用域時,變量 ten在第一個語句之前通過聲明和初始化步驟。因此,當調用 console.log(ten)時,打印 undefined。語句 ten=10指定一個初值。賦值之後, console.log(ten) 將正確地打印 10。

4. 函數聲明生命週期

在函數聲明語句 functionfunName(){...}的情況下,它比變量聲明生命週期更簡單。

JS變量生命週期:為什麼 let 沒有被提升

聲明、初始化和賦值階段同時發生在封閉函數作用域的開頭(只有一步)。可以在作用域的任何位置調用 funName(),而不依賴於聲明語句的位置(甚至可以在末尾調用)。

下面的代碼示例演示了函數提升:

function
sumArray
(
array
)

{

return
array
.
reduce
(
sum
);

function
sum
(
a
,
b
)

{

return
a
+
b
;

}
}
sumArray
([
5
,

10
,

8
]);

// => 23

當執行 sumArray([5,10,8])時,它進入 sumArray函數作用域。在這個作用域內,在任何語句執行之前, sum都會通過所有三個階段:聲明、初始化和賦值。這樣, array.reduce(sum)甚至可以在它的聲明語句 sum(a,b){…}之前使用 sum。

5. let 變量的生命週期

let 變量的處理方式與 var不同,主要區別在於聲明和初始化階段是分開的

JS變量生命週期:為什麼 let 沒有被提升

現在來看看一個場景,當解釋器進入一個包含 let變量語句的塊作用域時。變量立即通過聲明階段,在作用域中註冊其名稱(步驟1)。

然後解釋器繼續逐行解析塊語句。

如果在此階段嘗試訪問變量,JS 將拋出 ReferenceError:variableisnotdefined。這是因為變量狀態 未初始化,變量位於暫時死區 temporal dead zone

當解釋器執行到語句 letvariable時,傳遞初始化階段(步驟2)。變量退出暫時死區。

接著,當賦值語句 variable='value'出現時,將傳遞賦值階段(步驟3)。

如果JS 遇到 letvariable='value',那麼初始化和賦值將在一條語句中發生。

讓我們看一個例子,在塊作用域中用 let 聲明變量 number

let
condition
=

true
;
if

(
condition
)

{

// console.log(number); // => Throws ReferenceError

let
number
;
console
.
log
(
number
);

// => undefined
number
=

5
;
console
.
log
(
number
);

// => 5
}

當 JS 進入 if(condition){...} 塊作用域, number立即通過聲明階段。

由於 number已經處於單一化狀態,並且處於的暫時死區,因此訪問該變量將引發 ReferenceError:numberisnotdefined。接著,語句 letnumber進行初始化。現在可以訪問變量,但是它的值是 undefined。

const和 class 類型與 let具有相同的生命週期,只是分配只能發生一次。

5.1 提升在 let生命週期中無效的原因

如上所述,提升是變量在作用域頂部的耦合聲明和初始化階段。然而, let生命週期分離聲明和初始化階段。解耦消除了 let的提升期限。

這兩個階段之間的間隙產生了暫時死區,在這裡變量不能被訪問。

總結

使用 var聲明變量很容易出錯。在此基礎上,ES6 引入了 let。它使用一種改進的算法來聲明變量,並附加了塊作用域。

由於聲明和初始化階段是解耦的,提升對於 let變量(包括 const和 class)無效。在初始化之前,變量處於暫時死區,不能訪問。

為了保持變量聲明的流暢性,建議使用以下技巧


  • 聲明、初始化然後使用變量,這個流程是正確的,易於遵循。
  • 儘量隱藏變量。公開的變量越少,代碼就越模塊化。

番外

如何理解 let x = x 報錯之後,再次 let x 依然會報錯?


"

譯者:前端小智

原文:https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/

為了保證的可讀性,本文采用意譯而非直譯。

提升是將變量或函數定義移動到作用域頭部的過程,通常是 var 聲明的變量和函數聲明 functionfun(){...}。

當 ES6 引入 let(以及與 let類似聲明的 const和 class)聲明時,許多開發人員都使用提升定義來描述如何訪問變量。但是在對這個問題進行了更多的探討之後,令我驚訝的是提升並不是描述 let變量的初始化和可用性的正確術語。

ES6 為 let提供了一個不同的和改進的機制。它要求更嚴格的變量聲明,在定義之前不能使用,從而提高代碼質量。

1. 容易出錯的 var 提升

有時候我們會在zuo內作用域內看到一個奇怪的變量 varvarname和函數函數function funName(){...} 聲明:

// var hoisting
num
;

// => undefined
var
num
;
num
=

10
;
num
;

// => 10
// function hoisting
getPi
;

// => function getPi() {...}
getPi
();

// => 3.14
function
getPi
()

{

return

3.14
;
}

變量 num在聲明 varnum之前被訪問,因此它被賦值為 undefined。 fucntion getPi(){…}在文件末尾定義。但是,可以在聲明 getPi()之前調用該函數,因為它被提升到作用域的頂部。

事實證明,先使用然後聲明變量或函數的可能性會造成混淆。假設您滾動一個大文件,突然看到一個未聲明的變量,它到底是如何出現在這裡的,以及它在哪裡定義的?

當然,一個熟練的JavaScript開發人員不會這樣編寫代碼。但是在成千上萬的JavaScript中,GitHub repos是很有可能處理這樣的代碼的。

即使查看上面給出的代碼示例,也很難理解代碼中的聲明流。

當然,首先要聲明再使用。 let 鼓勵咱們使用這種方法處理變量。

2. 理解背後原理:變量生命週期

當引擎處理變量時,它們的生命週期由以下階段組成:


  1. 聲明階段(Declaration phase)是在作用域中註冊一個變量。
  2. 初始化階段(Initialization phase)是分配內存併為作用域中的變量創建綁定。在此步驟中,變量將使用 undefined自動初始化。
  3. 賦值階段(Assignment phase)是為初始化的變量賦值。

變量在通過聲明階段時尚未初始化狀態,但未達到初始化狀態。

JS變量生命週期:為什麼 let 沒有被提升

請注意,就變量生命週期而言,聲明階段與變量聲明是不同的概念。簡而言之,JS引擎在3個階段處理變量聲明:聲明階段,初始化階段和賦值階段。

3.var 變量的生命週期

熟悉生命週期階段之後,讓我們使用它們來描述JS引擎如何處理 var變量。

JS變量生命週期:為什麼 let 沒有被提升

假設JS遇到一個函數作用域,其中包含 var變量語句。變量在執行任何語句之前通過聲明階段,並立即通過作用域開始處的初始化階段(步驟1)。函數作用域中 var變量語句的位置不影響聲明和初始化階段。

在聲明和初始化之後,但在賦值階段之前,變量具有 undefined 的值,並且已經可以使用。

在賦值階段 variable='value' 時,變量接收它的初值(步驟2)。

嚴格意義的提升是指在函數作用域的開始處聲明並初始化一個變量。聲明階段和初始化階段之間沒有差別。

讓我們來研究一個例子。下面的代碼創建了一個包含 var語句的函數作用域

function
multiplyByTen
(
number
)

{
console
.
log
(
ten
);

// => undefined

var
ten
;
ten
=

10
;
console
.
log
(
ten
);

// => 10

return
number
*
ten
;
}
multiplyByTen
(
4
);

// => 40

開始執行 multipleByTen(4)並進入函數作用域時,變量 ten在第一個語句之前通過聲明和初始化步驟。因此,當調用 console.log(ten)時,打印 undefined。語句 ten=10指定一個初值。賦值之後, console.log(ten) 將正確地打印 10。

4. 函數聲明生命週期

在函數聲明語句 functionfunName(){...}的情況下,它比變量聲明生命週期更簡單。

JS變量生命週期:為什麼 let 沒有被提升

聲明、初始化和賦值階段同時發生在封閉函數作用域的開頭(只有一步)。可以在作用域的任何位置調用 funName(),而不依賴於聲明語句的位置(甚至可以在末尾調用)。

下面的代碼示例演示了函數提升:

function
sumArray
(
array
)

{

return
array
.
reduce
(
sum
);

function
sum
(
a
,
b
)

{

return
a
+
b
;

}
}
sumArray
([
5
,

10
,

8
]);

// => 23

當執行 sumArray([5,10,8])時,它進入 sumArray函數作用域。在這個作用域內,在任何語句執行之前, sum都會通過所有三個階段:聲明、初始化和賦值。這樣, array.reduce(sum)甚至可以在它的聲明語句 sum(a,b){…}之前使用 sum。

5. let 變量的生命週期

let 變量的處理方式與 var不同,主要區別在於聲明和初始化階段是分開的

JS變量生命週期:為什麼 let 沒有被提升

現在來看看一個場景,當解釋器進入一個包含 let變量語句的塊作用域時。變量立即通過聲明階段,在作用域中註冊其名稱(步驟1)。

然後解釋器繼續逐行解析塊語句。

如果在此階段嘗試訪問變量,JS 將拋出 ReferenceError:variableisnotdefined。這是因為變量狀態 未初始化,變量位於暫時死區 temporal dead zone

當解釋器執行到語句 letvariable時,傳遞初始化階段(步驟2)。變量退出暫時死區。

接著,當賦值語句 variable='value'出現時,將傳遞賦值階段(步驟3)。

如果JS 遇到 letvariable='value',那麼初始化和賦值將在一條語句中發生。

讓我們看一個例子,在塊作用域中用 let 聲明變量 number

let
condition
=

true
;
if

(
condition
)

{

// console.log(number); // => Throws ReferenceError

let
number
;
console
.
log
(
number
);

// => undefined
number
=

5
;
console
.
log
(
number
);

// => 5
}

當 JS 進入 if(condition){...} 塊作用域, number立即通過聲明階段。

由於 number已經處於單一化狀態,並且處於的暫時死區,因此訪問該變量將引發 ReferenceError:numberisnotdefined。接著,語句 letnumber進行初始化。現在可以訪問變量,但是它的值是 undefined。

const和 class 類型與 let具有相同的生命週期,只是分配只能發生一次。

5.1 提升在 let生命週期中無效的原因

如上所述,提升是變量在作用域頂部的耦合聲明和初始化階段。然而, let生命週期分離聲明和初始化階段。解耦消除了 let的提升期限。

這兩個階段之間的間隙產生了暫時死區,在這裡變量不能被訪問。

總結

使用 var聲明變量很容易出錯。在此基礎上,ES6 引入了 let。它使用一種改進的算法來聲明變量,並附加了塊作用域。

由於聲明和初始化階段是解耦的,提升對於 let變量(包括 const和 class)無效。在初始化之前,變量處於暫時死區,不能訪問。

為了保持變量聲明的流暢性,建議使用以下技巧


  • 聲明、初始化然後使用變量,這個流程是正確的,易於遵循。
  • 儘量隱藏變量。公開的變量越少,代碼就越模塊化。

番外

如何理解 let x = x 報錯之後,再次 let x 依然會報錯?


JS變量生命週期:為什麼 let 沒有被提升


這個問題說明:如果 let x 的初始化過程失敗了,那麼


  1. x 變量就將永遠處於 created 狀態。
  2. 你無法再次對 x 進行初始化(初始化只有一次機會,而那次機會你失敗了)。
  3. 由於 x 無法被初始化,所以 x 永遠處在暫時死區
  4. 有人會覺得 JS 坑,怎麼能出現這種情況;其實問題不大,因為此時代碼已經報錯了,後面的代碼想執行也沒機會。
"

相關推薦

推薦中...