一.var 聲明與變量提升機制
在JavaScript
中使用var
定義一個變量,無論是定義在全局作用域函數函數的局部作用域中,都會被提升到其作用域的頂部,這也是JavaScript
定義變量的一個令人困惑的地方。由於es5
沒有像其它類C
語言一樣的塊級作用域,因此es6
增加了let
定義變量,用來創建塊級作用域。
我們來看一個var定義變量的示例:
function setName(){
if(condition){
var name = 'loho';
console.log(name);
}else{
console.log(name);
}
}
var student = 'eveningwater';
setName();
以上代碼可以理解成如下:
var student;
function setName(){
var name;
if(condition){
name = 'loho';
console.log(name);//loho
}else{
console.log(name);//undefined
}
}
student = 'eveningwater';
setName();
二.塊級聲明
塊級聲明意在指定一個塊級作用域,使得塊級作用域中所定義的變量無法再全局被訪問到,塊級作用域也被稱爲詞法作用域。
塊級作用域存在於兩個地方:
- 函數內部。
- 指定代碼塊中。(即"{"和"}"之間的區域)
1.let 聲明
let
聲明同var
聲明用法一致,唯一的區別在於,let
聲明將變量限制在一個塊內,這樣就形成了一個塊級作用域,因此也就不會存在變量的提升了。
例如前面的示例,我們可以寫成如下:
let stundent = 'eveningwater';
function setName(){
if(condition){
let name = 'loho';
console.log(name);//loho
}else{
//如果條件爲false執行到這裏
console.log(name);//不返回值
}
}
setName();
2.禁止重聲明
在使用let
定義變量之前如果已經聲明瞭相同的變量,就會報錯。因此不能重複聲明變量。如以下示例:
var name = 'eveningwater';
//報錯,重複聲明
let name = 'loho';
當然這兩個變量必須是在同一個作用域中,如果是不同作用域中,則不會報錯。但有可能會遮蔽第一次聲明的變量。如以下示例:
var name = 'eveningwater';
if(condition){
//不會報錯
let name = 'loho';
}
3.const聲明
使用const
標識符所聲明的變量必須要初始化,因此這個聲明的就是一個常量。如下例:
const name='eveningwater';//正確
const name;//錯誤,未初始化
const
聲明同let
聲明一樣,也是創建了一個塊級作用域,在這個塊級作用域之外是無法訪問到所聲明的變量的。換句話說,就是const
所聲明的變量不會有變量提升機制。如下例:
if(condition){
const name = 'eveningwater';
console.log(name);//'eveningwater'
}
//錯誤
console.log(name);
同樣的const
也不能重複聲明,如下例:
var name = 'eveningwater';
//錯誤,不能重複聲明
const name = 'loho';
但也可以在不同作用域中重複聲明,如下例:
var name = 'eveningwater';
if(condition){
const name = 'loho';
console.log(name);//loho,屏蔽全局定義的變量
}
儘管const
聲明與let
聲明有太多相似的地方,但const
聲明也有一處與let
聲明不同,那就是const
聲明的變量不能被賦值,無論是在非嚴格模式下還是在嚴格模式下,都不能對const
聲明的變量進行賦值。如下例:
const name = 'eveningwater';
//錯誤
name = 'loho';
不過,如果定義的是一個對象,可以對對象的值進行修改,如下例:
const student = {
name:'eveningwater'
}
student.name = 'loho';//沒問題
//錯誤,相當於賦值修改對象
student = {
name:'loho'
}
4.臨時死區
前面提到let
和const
聲明的變量都不會提升到作用域的頂部,因此在使用這兩個標識符聲明之前訪問會報錯,即使是typeof
操作符也會觸發引用錯誤。如下例:
console.log(typeof name);//報錯
const name = 'eveningwater';
由於第一行代碼就報錯了,因此後續的聲明變量語句不會執行,此時就出現了JavaScript
社區所謂的"臨時死區"(temporal dead zone)
.雖然這裏示例是const
聲明,但let
聲明也是一樣的。
但如果在const
或let
聲明的變量的作用域之外使用typeof
操作符監測卻不會報錯,只不過會返回undefined
。如下例:
console.log(typeof name);//undefined
if(condition){
let name = 'eveningwater';
}
5.循環中的塊級作用域綁定
我們在使用var
聲明變量的時候,總會遇到這樣的情況,如下:
for(var i = 0;i < 100;i++){
//執行某些操作
}
//這裏也能訪問到變量i
console.log(i);//100
我們可以使用let
聲明將變量i
限制在循環中,此時再在循環作用域之外訪問變量i
就會報錯了,因爲let
聲明已經爲循環創建了一個塊級作用域。如下:
for(let i = 0;i < 100;i++){
//執行某些操作
}
//報錯
console.log(i);
6.循環中的創建函數
在使用var
聲明變量的循環中,創建一個函數非常的困難,如下例:
var func = [];
for(var i = 0;i < 5;i++){
func.push(function(){
console.log(i);
})
}
func.forEach(function(func){
func();
});
你可能預期想的是打印從0到5
之間,即0,1,2,3,4
的數字,但實際上答案並不是如此。由於函數有自己的作用域,因此在向數組中添加函數的時候,實際上循環已經運行完成,因此每次打印變量i
的值都相當於是在全局中訪問變量i
的值,即i = 5
這個值,因此實際上答案最終會返回5次5
.
在es5
中,我們可以使用函數表達式(IIFE)
來解決這個問題,因爲函數表達式會創建一個自己的塊級作用域。如下:
var func = [];
for(var i = 0;i < 5;i++){
(function(i){
func.push(function(){
console.log(i);
})
})(i)
}
func.forEach(function(func){
func();//這就是你想要的答案,輸出0,1,2,3,4
})
;
但事實上有了es6
的let
聲明,我們不必如此麻煩,只需要將var
變成let
聲明就可以了,如下:
var func = [];
for(let i = 0;i < 5;i++){
func.push(function(){
console.log(i);
})
}
func.forEach(function(func){
func();//輸出0,1,2,3,4
})
但是這裏不能使用const
聲明,因爲前面提到過,const
聲明並初始化了一個常量之後是不能被修改的,只能在對象中被修改值。如以下示例就會報錯:
//在執行循環i++條件的時候就會報錯
for(const i = 0;i < len;i++){
console.log(i);
}
因爲i++
這個語句就是在嘗試修改常量i
的值,因此不能將const
聲明用在for
循環中,但可以將const
聲明用在for-in
或者for-of
循環中。如下:
var func = [];
var obj = {
name:'eveningwater',
age:22
}
for(let key in obj){
func.push(function(){
console.log(key)
})
}
func.forEach(function(func){
func();//name,age
});
//以下也沒問題
var func = [];
var obj = {
name:'eveningwater',
age:22
}
for(const key in obj){
func.push(function(){
console.log(key)
})
}
func.forEach(function(func){
func();//name,age
});
這裏並沒有修改key
的值,因此使用const
和let
聲明都可以,同理for-of
循環也是一樣的道理。for-of
循環是es6
的新增的循壞。。
7.全局作用域綁定
let,const
聲明與var
聲明還有一個區別就是三者在全局作用域中的行爲。當使用var
聲明一個變量時,會在全局作用域(通常情況下是瀏覽器window對象)中創建一個全局屬性,這也就意味着可能會覆蓋window
對象中已經存在的一個全局變量。如下例:
console.log(window.Array);//應該返回創建數組的構造函數,即f Array(){}
var Array = '這是數組';
console.log(window.Array);//返回'這是數組';
從上例,我們可以知道即使全局作用域中已經定義了Array
變量或者已經存在了Array
屬性,但我們之後定義的Array
變量則會覆蓋之前已經定義好的或者已經存在的Array
變量(屬性)。
但是es6
的let
和const
聲明則不會出現這種情況,let
和const
聲明會創建一個新的綁定,也就是說不會成爲window
對象的屬性。換句話說,就是所聲明的變量不會覆蓋全局變量,而只會遮蔽它。如下例:
let Array = '這是數組';
console.log(Array);//'這是數組‘;
console.log(window.Array);//應該返回創建數組的構造函數,即f Array(){}
這也就是說window.Array !== Array
這個等式返回布爾值true
。
8.塊級綁定的最佳實踐
在使用es6
塊級聲明變量中,最佳實踐是如果確定後續不會改變這個變量的值,用const
聲明,如果確定要改變這個變量的值,則用let
聲明。因爲預料外的變量值的改變時很多bug
出現的源頭。如下示例:
function eveningWater(){};
eveningWater.prototype.name = 'eveningwater';
let ew = new eveningWater();
//定義不能被修改的變量,也就是用於判斷實例類型的屬性
const _constructor = ew.constructor;
//可以改變自定義的名字屬性
let name = ew.name;
if(_constructor === eveningWater || _constuctor === Object){
console.log(_constructor);
}else{
name = 'loho';
console.log(name)
}