Javascript(JS) let, const 跟 var 的比較跟應用

Posted by Anthony Chao on 2019-12-05

let, const 跟 var 的比較跟應用

最近黑色星期五花錢用半價買了 Wes Bos 的 ES6 課程,所以順手來紀錄下自己的所學

眾所皆知 letconst 是兩個 ES6 語法中才新增宣告變數的方式,而 var 是舊有的方式
這年頭流行先講結論,所以我先說這三者使用時機:

使用時機

  1. 優先使用 const
  2. 如果需要更新變數的時候才用 let
  3. 在 ES6 裡面盡量不要使用 var

這時機是 Wes Bos 建議的,但我目前也同意
接著要講的是這三者的不同點:

不同點1: 作用域

var: function scoped
let / const: block scoped

這也是大家比較熟悉的,在以往 JS 的切分方法就只有 function ,直到 let / const 出來才有 block scope 的概念

以下舉例 function scoped 跟 block scoped 的差異:

function scoped:

1
2
3
4
5
6
function setWidth(){
var width = 100 // 只活在 function 裡面
console.log(width)
}
console.log(width) // width is not defined
setWidth() // 100

Block scoped:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var age = 100
if(age > 12){
var dogYears = age * 7
}
console.log(dogYears) // 700

var age = 100
if(age > 12){
let dogYears = age * 7
}
console.log(dogYears) // dogYears is not defined

var age = 100
if(age > 12){
const dogYears = age * 7
}
console.log(dogYears) // dogYears is not defined

不同點2: var 可以重複宣告,但 let /const 會出錯

這三者裡面只有 const 宣告的常數是不能更新的
除此之外可以看下面範例,只有 var 可以在那邊對同一個變數 var 來 var 去, let 跟 const 都會跑出錯誤訊息

1
2
3
4
5
6
7
8
9
10
var me = "you"
var me = "you" // 沒事

let me = "you"
let me = "you" //Identifier 'me' has already been declared

const me = "you"
const me = "you" //Identifier 'me' has already been declared
me = "he" // Assignment to constant variable.
// 只有 const 是不能更新的

不同點3: var 跟 let / const hoisting 的表現不同

感謝 Huli 的 我知道你懂 hoisting,可是你了解到多深? 這篇文章,真的有種醍醐灌頂的感覺,下面也會引用他的話跟例子

這邊直接先引用 Huli 大的其中一句話講結論:

let 與 const 也有 hoisting 但沒有初始化為 undefined,而且在賦值之前試圖取值會發生錯誤。

以下舉例:
例1: 從頭到尾沒宣告變數使用的狀況

1
console.log(a) // ReferenceError: a is not defined

例2: 傳統的 hoisting

1
2
console.log(a)
var a = 2 // undefined

例3: 把 var 改成 let

1
2
console.log(a)
let a = 2 // ReferenceError: a is not defined

看到這邊一定覺得我在話唬爛,明明就沒有 hoisting XD
不對!真的有!但不太容易察覺,除非像是下面這個例子

1
2
3
4
5
6
var a = 10
function test(){
console.log(a)
let a
}
test() // ReferenceError: a is not defined

如果沒有 hoisting 行為的話,a 應該要讀取到外面的 10 而不是 ont defined 的結果,所以結論跟上面講過的一樣:用 let / const 來宣告的話還是會有 hoisting 的狀況,但不會初始化成 undefined

接下來來看看如何使用 closure

Closure(閉包) 的使用

一般來說,我們可能會直接宣告一個變數,可是這樣不好,因為這變數會變成 global function,很容易造成不好的結果,比方說我可能不小心宣告一個 name 的變數,但實際上已經有其他功能需要用到原本的 name 變數

因此我們想要把變數鎖在一個範圍裡面不 leak 到外面

如果是 var 我們需要用到 IIFE (立刻被呼叫的函式)

1
2
3
4
5
(function(){
var name = 'qoo'
console.log(name)
})()
// 'qoo'

如果是 let 或者 const 我們就用 Block 即可

1
2
3
4
5
{
let name = 'qoo'
console.log(name)
}
// 'qoo'

這可以延伸到在教初學者時常會講到的 setTimeout 案例

經典的 setTimeout 問題

我們看看下面兩種 for loop 執行結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for(var i = 0; i < 10; i++){
console.log(i)
setTimeout(function(){
console.log(`Number${i}`)
}, 1000)
}
// 0 1 2 3...10 Number10 x 10 次(一秒後)

for(let i = 0; i < 10; i++){
console.log(i)
setTimeout(function(){
console.log(`Number${i}`)
}, 1000)
}
// 0 1 2 3...10 Number0 Number1 ... Number 9(一秒後)

為什麼使用 var 的結果會這樣呢,因為他很快就把 for loop 執行完了,var是 function scope ,所以每次更改 i 值都會直接更改 Block 裡面的 i
但是 let 是 block scope,所以變數會被存在這個 block 裡面
那要怎麼修正他們,除了講出 Number 1 到 9 還要每隔一秒做一次呢?
let 比較簡單,只要把秒數乘上去就好:

1
2
3
4
5
for(let i = 0; i < 10; i++){
setTimeout(function(){
console.log(`Number${i}`)
}, 1000 * i)
}

var 就要用到 IIFE 了
在 for 把 i 丟給 block 的當下就傳進去 IIFE 裡面存起來

1
2
3
4
5
6
7
for(var i = 0; i < 10; i++){
(function(x){
setTimeout(function(){
console.log(`Number${x}`)
}, 1000 * x)
})(i)
}

好像是第一次研究 JS 這種基本的問題,實際寫一次也會讓自己知道是不是還有哪裡不清楚的地方

如果有哪裡有問題或者寫錯的也歡迎聯絡我,謝謝!

參考資料:

  1. Wes Bos ES6 課程
  2. 我知道你懂 hoisting,可是你了解到多深?
  3. 0 陷阱!0 誤解!8 天重新認識 JavaScript!




prevent_hack