提高其實是把變量和函數定義移動到做用域頂部的過程,一般發生在變量聲明var
或函數聲明function fun() {...}
。bash
當let
(包括和let
有一樣聲明行爲的const
和class
)被ES2015提出來的時候,包括我在內的許多開發人員都使用提高來描述變量是如何被訪問的。可是在對這個問題進行更多的搜索以後,讓我驚訝的是,提高並非描述let
變量初始化和可用性的正確術語。模塊化
ES2015給let
提供了不一樣而且改善的機制。它要求更嚴格的變量聲明實踐(在定義以前不能使用),所以會有更高質量的代碼。函數
咱們去看看這個過程當中更多的細節。ui
var
提高有時候我會看見一個奇怪的實踐,變量var varname
和函數function funcName() {...}
在做用域任意地方聲明:spa
// var hoisting
num; // => undefined
var num;
num = 10;
num; // => 10
// function hoisting
getPi; // => function getPi() {...}
getPi(); // => 3.14
function getPi() {
return 3.14;
}
複製代碼
num
變量在var num
聲明以前被訪問,因此被認定爲undefined
。code
函數function getPi() {...}
被定義在末尾。因爲它被提高到做用域的頂部,因此函數能在定義getPi()
以前被調用。cdn
這就是典型的提高。blog
事實證實,先使用後聲明變量或函數可能會形成混亂。假設您在滾動查看一個大文件,忽然看到一個未聲明的變量...它是怎麼出如今這裏,又是在哪裏定義的?生命週期
固然一個經驗豐富的JavaScript開發者不會用這種方式寫代碼,可是在成千上萬的JavaScript GitHub倉庫中頗有可能會面對這樣的代碼。
即便看着上面給出的代碼示例,也很難理解代碼中的聲明流程。
天然地,首先聲明或描述一個未知的詞語,而後才使用它來編寫短語。let
鼓勵遵循這種方式來使用變量。
當引擎訪問變量的時候,它們的生命週期包括下面幾個階段:
undefined
經過聲明階段可是沒有到達初始化階段的變量是處於未定義的狀態。
注意,根據變量的生命週期,聲明階段和通常說的變量聲明是不一樣的術語。簡言之,引擎在三個階段處理變量聲明:聲明階段、初始化階段和賦值階段。
var
變量的生命週期熟悉了生命週期階段,咱們用它們來描述引擎是怎樣處理var
變量的。
var
聲明的變量的函數。在任何語句執行以前,這個變量就經過了聲明階段和初始化階段(第一步)。
var variable
在函數做用域中聲明的位置不影響聲明和初始化階段。
在聲明和初始化以後,賦值階段以前,變量值爲undefined
而且能夠被訪問使用。
在賦值階段,variable = 'value'
,變量會獲得它的初始值(第二步)。
嚴格來講,提高的概念是在函數做用域的頂部聲明和初始化變量。在聲明和初始化階段之間沒有間隙。
來研究一個例子。下面的代碼建立了一個帶有var
變量的函數:
function multiplyByTen(number) {
console.log(ten); // => undefined
var ten;
ten = 10;
console.log(ten); // => 10
return number * ten;
}
multiplyByTen(4); // => 40
複製代碼
當JavaScript開始執行multipleByTen(4)
的時候,它就進入了multipleByTen
的函數做用域,在第一條語句以前,變量ten
完成了聲明和初始化階段。因此調用console.log(ten)
會打印出undefined
。
語句ten = 10
分配了一個初始值。分配以後,console.log(ten)
正確地輸出了10。
假設一個函數聲明語句function funName() {...}
,這更加容易。
funcName()
能夠在該做用域的任何地方調用,不依賴於聲明語句的位置(甚至能夠在最後)。
下面的代碼示例展現了函數提高:
function sumArray(array) {
return array.reduce(sum);
function sum(a, b) {
return a + b;
}
}
sumArray([5, 10, 8]); // => 23
複製代碼
當JavaScript調用sumArray([5, 10, 8])
的時候,它就進入了sumArray
的函數做用域。在做用域裏面,在全部語句執行以前,sum
經過了三個階段:聲明、初始化、賦值。 這種方式,array.reduce(sum)
甚至能夠在聲明語句function sum(a, b) {...}
以前使用sum
。
let
變量的生命週期let
變量的處理方式和var
不一樣。最主要的區別就是聲明和初始化階段被分開了。
let variable
語句。變量馬上經過了聲明階段,在做用域註冊了它的名字(步驟 1)。
接着解釋器一行一行的解析語句。
若是在這個階段嘗試訪問變量variable
,JavaScript會拋出ReferenceError: variable is not defined
。由於變量的狀態是未定義的,variable
在暫時性死區。
當解釋器到達let variable
語句的時候,初始化階段經過(步驟 2)。如今變量的狀態是已定義而且訪問它會獲得undefined
。
變量退出了暫時性死區。
稍後,當賦值語句variable = 'value'
出現,就經過了賦值階段(步驟 3)。
若是JavaScript遇到let variable = 'value'
,那麼初始化和賦值會發生在這一條語句上。
來看一個例子。在塊級做用域裏面用let
聲明一個變量variable
:
let condition = true;
// console.log(number); // => Throws ReferenceError
let number;
console.log(number); // => undefined
number = 5;
console.log(number); // => 5
}
複製代碼
當JavaScript進入if (condition) {...}
塊級做用域的時候,number
馬上經過了聲明階段。 由於number
處於未定義的狀態,處於暫時性死區,試圖訪問這個變量會拋出ReferenceError: number is not defined
。
後來,語句let number
完成了初始化。如今這個變量能夠訪問了,可是它的值是undefined
。
賦值語句number = 5
固然就是完成了賦值階段。
const
和class
類型和let
有相同的生命週期,除了賦值只能發生一次。
let
的生命週期裏無效如上所述,提高就是變量在做用域頂部進行聲明和初始化。可是let
的生命週期將聲明和初始化兩個階段解耦了。解耦讓提高這個術語失效了。
兩個階段中間的間隙建立了暫時性死區,在這裏,變量不能被訪問。
以一種科幻的風格,在let
生命週期中,提高這個術語的崩塌創造了暫時性死區。
隨意使用var
聲明變量容易犯錯。基於這個教訓,ES2015建立了let
。它使用一種改進的算法來聲明變量,而且使用塊級做用域。
因爲聲明和定義兩個階段解耦,提高對於一個let
聲明的變量(包括包括const
和class
)無效。在初始化以前,變量處於暫時性死區而且不能夠訪問。
保持穩定的變量聲明,有以下建議: