1. var聲明及變量提升機制
提升(Hoisting)機制:通過關鍵字var聲明的變量,都會被當成在當前作用域頂部生命的變量。
function getValue(condition){if(condition){var value = "blue";console.log(value);}else{// 此處可訪問變量value,其值為undefinedreturn null}// 此處可訪問變量value,其值為undefined
}
JavaScript引擎會將上面的getValue函數修改為下面這樣。變量value的聲明被提升至函數頂部,初始化操作依舊保留在原處執行。為此,ES6引入了塊級作用域來強化對變量聲明周期的控制。
function getValue(condition){var value;if(condition){value = "blue";console.log(value);}else{return null}
}
?
2. 塊級聲明
塊級聲明用于聲明在指定塊的作用域之外無法訪問的變量。
塊級作用域存在于:
- 函數內部
- 塊中(‘{}’之間的區域)。
2.1. let聲明
- 用法同var相同,但聲明不會被提升;
- 禁止在同一作用域內重聲明;
- 如果當前作用域內嵌另一個作用域,便可在內嵌的作用域中用let聲明同名變量。
function getValue(condition){if(condition){var value = "blue";console.log(value);}else{// 變量value在此處不存在return null}// 變量value在此處不存在
}
/**禁止重聲明*/var count = 30;
// 拋出語法錯誤
let count = 40;if(condition){// 不會拋出錯誤let count = 40;
}
2.2. const聲明
- 聲明的是常量,必須初始化;
- 禁止在同一作用域內重聲明;
- 不可再賦值(常量對象可修改值;
- const聲明對象時,不允許修改綁定,但可修改值。
// 有效的常量
const maxItems = 30;
// 語法錯誤,未初始化
const name;if(condition){const cnt = 40;
}
// 在此處無法訪問cntlet age = 20;
// 拋出錯誤,重聲明
const age = 15;const pos = 30;
// 拋出語法錯誤,不能重新賦值
pos = 35;const person = {name: 'Nicholas'
};
// 可以直接修改對象屬性的值
person.name = 'Fleur';
// 直接給person賦值,即要改變person的綁定,會拋出語法錯誤。
person = {name: 'DpprZ'
}
?
3. 臨時死區(Temppral Dead Zone, TDZ)?
由于 console.log(typeof value) 語句會拋出錯誤,因此用 let 定義并初始化變量 value 的語句不會執行。此時的 value 還位于 JavaScript 社區所謂的“臨時死區”。TDZ 通常用來描述 let 和 const 的不提升效果。
if(condition){console.log(typeof value); // 引用錯誤let value = 40;
}
console.log(typeof value); // "undefined"
if(condition){let value = 40;
}
?JS引擎掃描代碼發現變量聲明時:
- var聲明:將他們提升至作用域頂部。
- let和const聲明:將聲明放在TDZ中。訪問TDZ中的變量會觸發運行時錯誤。只有執行過變睪聲明語句后,變量才會從TDZ中移除,然后方可正常訪問。
?
4. 循環中的塊級作用域綁定
for循環中通過let將計數器變量限制在循環內部。
for(var i = 0; i < 10; i++){// 更多代碼
}
// 在這里仍然可以訪問變量i
console.log(i); // 10for(let i = 0; i < 10; i++){// 更多代碼
}
// i在這里不可訪問,拋出錯誤
console.log(i);
?
5. 循環中的函數、let聲明、const聲明
let funcs = [];
for(var i = 0; i < 10; i++){funcs.push(function(){console.log(i)})
}
/*
每個funcs[]中都存在一個函數:? (){ console.log(i) }
*/
funcs.forEach(function(func){func(); // 10個10
})
因為這里的循環里的每次迭代同時共享著變量i,循環內部創建的函數都保留了對相同變量的引用。
解決該問題的兩種方案:
在循環中使用立即調用函數表達式(IIFE),以強制生成計數器變量的副本。
let funcs = [];for(var i = 0; i < 10; i++){funcs.push(function(value){return function(){console.log(value)}})
}funcs.forEach(function(func){func(); // 0 1 2 3......9
})
用let聲明計數器:每次迭代循環都會創建一個新變量 i,并以之前迭代同名變量的值將其初始化。所以循環內部創建的每一個函數都能夠得到屬于自己的 i 的值。let 聲明在循環內部的行為是標準中專門定義的,它不一定與let的不提升特性相關,理解這一點至關重要!
let funcs = [];for(let i = 0; i < 10; i++){funcs.push(function(){console.log(i)})
}funcs.forEach(function(func){func(); // 0 1 2 3...... 9
})
- ?let聲明:聲明計數器、for-in、for-of
- const聲明:生命循環內不改變的值、for-in、for-of
?
6. 全局塊作用域綁定
var、let、const在全局作用域中的行為區別:
var會創建一個新的變量作為全局對象(瀏覽器環境中的window對象),會無意中覆蓋已存在的全局屬性。
let、const會在全局作用域下創建一個新的綁定,但該綁定不會添加為全局對象的屬性。換句話說,不能覆蓋只是遮蔽。?