ES6 的 let
、const
是為了改變 var
在宣告變數上的一些問題。
var
具函式作用域,所以不受區塊限制,但會受到函式範圍限制。var
也是一種宣告變數的方式,可覆寫值,與 let 類似但相對較不嚴謹,但目前已經很少使用 var
因為較容易發生奇怪的問題 ( var
會汙染全域變數,容易造成不可預期的錯誤 )。程式量大的時候你可能會忘記取過什麼變數,所以會出現重複宣告的情況,蓋掉之前寫的變數的值。
var name = 'abc';
var name = 'ccc';
// var 是允許重複宣告變數的,且 console 不會丟任何錯誤提示給你,而 let 和 const 會。
var
在函式外宣告屬於 global variables(全域變數,意即 JS 任何地方都可以使用),在函式內則為該函式整個區域都可以使用 → 這是屬於 scope(作用域的範疇)。主要都是圍繞在 Redeclartion(重新宣告)、Scope(作用域)、Hoisiting(提升)、TDZ 這幾個主題,有興趣的話也可以查查這些關鍵字。
function sayHi() {
var name = "andy";
for (var i = 1; i<=3; i++) {
var num = 0;
if (i = 3) {
num = i;
}
}
console.log(`${name}的座號是 ${num} 號`);
// 可以取得 for 迴圈的變數 num,若是改用 let 宣告 num,它是取不到 for 迴圈內的 num 的,會報錯
}
sayHi();
for (var i = 0; i < 10; i++){
console.log(`for 迴圈內 ${i}`)
}
console.log(`for 迴圈外 ${i}`)
for 迴圈使用 var 宣告 i
會污染全域:
var i = 0;
中的 i
因為是用 var
宣告,所以為全域變數,在 for 迴圈外也取得到,所以如果想要把 i 控制在 for 迴圈內就會產生一些問題。
window.i
來查看並找到 i
的值。範例 2. 出處:JavaScript 核心篇 / Let, Const 基本概念
var answer = true;
if (answer) {
var myFeedback = '同意';
console.log(`判斷是內:${myFeedback}`);
}
console.log(`判斷是外:${myFeedback}`);
除了上方的 for 迴圈,在判斷式也會有相同問題 → 污染全域
let
宣告的變數可重新賦予新的值。let
宣告過的變數,並重新賦予新的值。但不可使用 let
再重新宣告相同的變數,會出現錯誤訊息 Uncaught SyntaxError: Identifier 'myName' has already been declared
,這樣就可以避免同一個作用域下使用 let 做重覆宣告。// 正確用法:宣告過的變數重新賦予新的值
let ricePrice = 100;
ricePrice = 150;
console.log(ricePice); // 150
// 錯誤用法,使用let 重新宣告相同的變數
let myName = 'Carrie';
let myName = 'CarrieT';
const
是宣告一個常數,所以基本上使用 const
宣告的變數是沒辦法被調整的。( cosnt 在原始型別難以被覆寫 )
隨時需要做調整的變數值的話可使用 let
,不會去更改值的話可使用 const
。
const
在 object {}
與 Array []
中使用是可被修改的。除非使用 Object.freeze(變數);
會凍結裡面的內容無法做修改。
// 在 object {}
與 Array []
中使用是可被修改的 ▲
// 使用 Object.freeze(變數);
會凍結裡面的內容無法做修改 ▲
{}
大括號的範圍。var
宣告的變數具有函式作用域的特性,代表切分變數有效範圍的最小單位是 function
。{}
block。
var
具函式作用域,所以不受區塊{}
限制,但會受到函式function
範圍限制。
因為 var 具函式作用域,所以上方「var 可能會產生的問題 」中範例 1、2、3 方式都會污染全域,除非用 function
包住。可見 function 外就讀取不到 i
的變數 ( i is not defined
)。
範例1.
// 這邊使用立即函式包覆
// 也可移除立即函式把 var 調整為 let
(function(){
for (var i = 0; i < 10; i++){
console.log(`for 迴圈內 ${i}`)
}
})()
console.log(`for 迴圈外 ${i}`)
範例 2.
var
具函式作用域,所以不受區塊限制,但會受到函式範圍限制
// --- var不受區塊限制,但會受到函式範圍限制
// --範例一
function call() {
var isCall = 'Carrie';
}
console.log(isCall); // Uncaught ReferenceError: isCall is not defined
// --範例二
{
var isCall = 'Carrie';
}
console.log(isCall ); //Carrie
範例 1.
const
、let
具區塊作用域,所以有效作用域範圍會被限制在該區域中
// --範例一
{
let isCall = 'Carrie';
}
console.log(isCall );
// --範例二
function call(){
const isCall = 'Carrie';
}
console.log(isCall );
// 皆印出 Uncaught ReferenceError: isCall is not defined
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(`這執行第 ${i} 次`);
}, 0);
}
console.log(i);
解析:
for 迴圈中使用 var
宣告變數 i
是全域變數,所以 for 迴圈外 console.log(i);
中的 i
會是執行到最後的結果 10 ( 為 0 到 9 個執行一次的狀態 )。
setTimeout
為非同步的程式, JS 會放到事件緒列內,等到所有程式都執行完才回來執行這個非同步程式。所以 setTimeout
中的 i
會是全域變數的 i
並不是 for 迴圈內的 i
。答案:
console.log(i);
會印出 10
、setTimeout
會印出 10 次 這執行第 10 次
。for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(`這執行第 ${i} 次`);
}, 0);
}
console.log(i);
解析:
let
宣告變數 i
就不會是全域變數,因為 let
為區塊作用域只會在區塊 {}
內產生作用答案:
setTimeout
會依序印出 這執行第 0 次
~ 這執行第 9 次
。console.log(i);
會印出 Uncaught ReferenceError: i is not defined
。
let
宣告的關係,所以 i
並非全域變數。Hoisting 分創造與執行兩階段,下方實作範例中來看看 var
與 let
宣告會有什麼不同處。
console.log(Ming);
var Ming = '小明';
解析:
Hoisting 分創造與執行兩階段,以上方程式碼來說會拆分為下面形式:
var Ming;
Ming = '小明';
var Ming;
為 undefined
,如果在賦予值前就先 console.log(Ming);
要去取 Ming
的值就會印出 undefined
。//----- 創造階段
var Ming;
//----- 執行階段
console.log(Ming);
Ming = '小明';
答案:
印出 undefined
console.log(Ming);
let Ming = '小明';
解析:
let
在創造階段會產生「 暫時性死區 TDZ 」,在此區域是無法取得值的。let
類似 Hoisting 提升的概念,只是它在提升時不會賦予變數 undefined
的值,而是出現「 暫時性死區 TDZ 」,這個暫時性死區無法存取這個變數。//----- 創造階段
let Ming; // 暫時性死區 TDZ
//----- 執行階段
console.log(Ming);
let Ming = '小明';
答案:
會顯示錯誤訊息 Uncaught ReferenceError: Cannot access 'Ming' before initialization
( 無法在初始化前去取得此變數 )。
console.log(typeof a);
console.log(typeof myName);
let myName = '';
解析:
let
一樣有創造階段,但 let 在創造階段會產生「 暫時性死區 TDZ 」,在此區域是無法取得值的,所以會顯示錯誤訊息 Uncaught ReferenceError: Cannot access 'Ming' before initialization
答案:
console.log(typeof a);
印出 undefined
。console.log(typeof myName);
印出錯誤訊息 Uncaught ReferenceError: Cannot access 'Ming' before initialization
( 無法在初始化前去取得此變數 )。a();
let a = function () {
console.log('a');
}
答案:
//--創造
let a;
//--執行
a();
a = function () {
console.log('a');
}
印出:Uncaught ReferenceError: Cannot access 'a' before initialization
console.log('a');
const a = 'Casper';
答案:
印出:Uncaught ReferenceError: Cannot access 'a' before initialization
let a;
console.log(a);
答案:
undefined
//--創造
let a;
//--執行
console.log(a);
const a;
console.log(a);
答案:
Uncaught SyntaxError: Missing initializer in const declaration
( const 宣告缺少初始化 )
const array = [];
array.push('Casper');
console.log(array);
答案:
[’Casper’]
let a = 10;
function fu() {
console.log(a);
let a = 20;
}
fu();
答案:
//創造
function fu() {
console.log(a);
let a = 20;
}
let a;
//執行
a = 10;
fu();
let
在創造階段為暫時性死區,
function fu() {
// 創造
let a; //暫時性死區
// 執行
console.log(a);
a = 20;
}
另外 let
為區塊作用域,在 {}
執行完記憶體就會釋放掉。所以答案為 Uncaught ReferenceError: Cannot access 'a' before initialization
。
function fu() {
}
fu.fu = 'QQ';
const a = fu;
a.fu = 'Casper';
console.log(a.fu);
答案:
Casper
。a.fu
會覆蓋掉前面的 fu.fu
。