iT邦幫忙

2021 iThome 鐵人賽

DAY 1
0
自我挑戰組

Ruby on Rails JS系列 第 1

JS let var const的不同

let 用於宣告一個「只作用在當前區塊的變數」,初始值可選擇性的設定。

以 let 宣告的變數,其作用域是「區塊作用域(block scope)」,也就是 { } 包住的區域,一但離開 { }範圍,這個變數就不會被存取到。

function:

function varTest() {
  var x = 1;
  {
    var x = 2;  // 這裡的 x 與 function 區塊內部的 x 是一樣的,因此會影響 function 區塊內所有的 x
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  {
    let x = 2;  // 這裡的 x 與 function 區塊內部的 x 是不同的,只會作用在這層 block 區塊中
    console.log(x);  // 2
  }
  console.log(x);  // 1
}
Copy to Clipboard
在上列例子裡的最前行 let 和 var 不同,let 並不會在全域物件中建立變數。舉例來說:

var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

var

以 var 宣告的變數,其作用域是「函式作用域(function scope)」,也就是在function 內宣告的 var,要在該 function 才有作用 ,但是如果在函式外宣告的話,其作用範圍則為 全域性(global)。

在函數之外使用以 var 宣告的變數是非強制的 (optional); 如果對一個未經宣告的變數賦值, 它會被暗中 (implicitly) 宣告成為一個全域變數 (亦即成為全域物件的屬性)。其中差異在於, 已宣告的變數是全域物件裡的一個無法變更 (non-configurable) 的屬性, 而未宣告的變數則是可變更的 (configurable)。

因此, 建議你一定要宣告你的變數, 不管你要將它使用於全域範圍內或者函數內。
開發時用var宣告變數會容易導致意外汙染全域變數的問題,例如是區域變數覆蓋全域變數。

var food = 'apple';
function func(){
    var result = 'I eat ' + food
    console.log(result)

}
func(); //I eat apple

這個例子很簡單,在func函式裏用到全域變數food,組合字串及回傳。但如果程式碼變得更複雜時,又或者另一個開發者沒注意到food已經在第一行宣告過了,就可能會出現以下的問題:

var food = 'apple';

// 200行code之後

function func(){
    var result = 'I eat ' + food
    console.log(result) // I eat undefined

    // 100行code之後,我忘了之前已經宣告過food
    var food = 'banana';
}
func(); 

在func這個函式裹的最後一行程式碼,我們再次宣告food這個變數及重新賦值,根據hoisting(提升)的概念,在函式裹的var food會提升至函式作用域裹的最高處,在「提升之後(var food)」到「賦值之前food = 'banana'」,這段期間food的值會是undefined。我們可以把整個過程想像如下圖:

var food = 'apple';

function func(){
    var food 
    // 提升之後 
    var result = 'I eat ' + food
    console.log(result) // I eat undefined

    // 賦值之前
    food = 'banana';
}

func(); 

所以在函式裹第二行的food會變成undefined。

澄清一點,food變成了undefined這個問題,並不是因為重複宣告變數。我在函式內再次宣告在函式外的變數是不會報錯的,如下面的做法:

var food = 'apple';

// 200行code之後

function func(){
    // 100行code之後,我忘了之前已經宣告過food
    var food = 'banana';
    var result = 'I eat ' + food
    console.log(result) // I eat banana
}
func();

以上的例子,我把var food = 'banana'放在var result = 'I eat ' + food前面,這裹會成功回傳I eat banana。因為我是在變數food被賦值'banana'之後(用=去賦予),才提取food這個變數,這時候food已經是'banana'。總括來說,這個問題出現與否是取決於你在什麼時候提取這個變數。重複一次,如果你在「提升之後(var food)」到「賦值之前food = 'banana'」提取變數,就會變成undefined。

const

宣告 const 會對於它的值建立一個唯讀的參考。並不是說這個值不可變更,而是這個變數不能再一次指定值。例如,假設常數的內容(值)是個物件,那麼此物件的內容(物件的參數)是可以更改的。

// 注意: 常數可以宣告成大寫或小寫,
// 但習慣上使用全部大寫的字母。

// 定義一個常數 MY_FAV 並賦予它的值為7
const MY_FAV = 7;

// 這裡會發生錯誤 - Uncaught TypeError: Assignment to constant variable.
MY_FAV = 20;

// MY_FAV 是 7
console.log('我喜歡的數字是: ' + MY_FAV);

// 嘗試重複宣告同名的常數,將會發生錯誤 -  Uncaught SyntaxError: Identifier 'MY_FAV' has already been declared
const MY_FAV = 20;

// MY_FAV 這個名稱已經保留給上面的常數, 所以這裡也會錯誤。
var MY_FAV = 20;

// 這式子也會錯誤
let MY_FAV = 20;

// 很重要,請注意區塊可視範圍的特性。
if (MY_FAV === 7) {
    // 以下式子沒有問題,並且會建立一個名叫 MY_FAV 的具有區塊可視範圍的變數。
    // (等同於使用 let 來宣告一個具有區塊可視範圍的非常數變數。)
    let MY_FAV = 20;

    // MY_FAV 現在變成 20
    console.log('我喜歡的數字是:' + MY_FAV);

    // 這會將變數懸掛於全域,而導致錯誤。(與常數同名)
    var MY_FAV = 20;
}

// MY_FAV 仍然是 7
console.log('我喜歡的數字是:' + MY_FAV);

// 發生錯誤 - Uncaught SyntaxError: Missing initializer in const declaration
const FOO;

// 常數的值可以是一個物件
const MY_OBJECT = {'key': 'value'};

// 嘗試覆寫該物件將會發生錯誤 - Uncaught TypeError: Assignment to constant variable.
MY_OBJECT = {'OTHER_KEY': 'value'};

// 然而, 物件的屬性並沒有被保護,
// 所以,以下敘述式沒有問題。
MY_OBJECT.key = 'otherValue'; // Use Object.freeze() to make object immutable

// 對陣列來說也是一樣
const MY_ARRAY = [];
// 可以把項目加到陣列中。
MY_ARRAY.push('A'); // ["A"]
// 然而,對這個變數指定新陣列,將會發生錯誤 - Uncaught TypeError: Assignment to constant variable.
MY_ARRAY = ['B'];

參考資料

mdn let
mdn var
mdn const
JavaScript基本功修練:Day5 - 宣告變數 - let、const、var


下一篇
MVC架構
系列文
Ruby on Rails JS29

尚未有邦友留言

立即登入留言