ES6 里新增了兩種聲明變量的方式,let 和 const,加上原來的 var,一共就有三種方式來聲明變量了。那到底該用哪個呢?關于“盡可能不用 var” 這一點,大家應該沒有什么意見分歧(其實還是有少數人不這么想的),關于“是用 let 還是用 const”,社區里主要有兩種不同的觀點:
1. 默認全用 let,只在符合一些寫代碼的人的主觀判斷條件的時候用 const,下面舉個這樣的“主觀判斷條件”的例子(實際代碼中用到 const 的幾率大概會是 0.1%):
- 你能 100% 確定該變量永遠不會在其它的代碼行里被重新賦值,但是該變量的初始值有可能在未來會被調整(有點配置項的意思),且
- 該變量的初始值是個數字字面量(或者多個數字組成的數學運算表達式)、字符串字面量、布爾字面量、數組字面量、對象字面量、正則字面量、symbol "字面量"(打引號是因為 symbol 其實沒有字面量),且
- 該變量是個全局變量,或者是模塊內的全局變量
代碼舉例:
const VERSION = 2 const TIMEOUT = 10000 const MB = 1024 * 1024 const DAY = 24 * 60 * 60 * 1000 const COLOR = "rgb(255,255,255)" const HTMLNS = "http://www.w3.org/1999/xhtml" const BUTTONS = ["left", "middle", "right"] const SIDES = ["left", "right", "top", "bottom"]
2. 默認全用 const,只有該變量需要被重新賦值才用 let (實際代碼中用到 const 的幾率大概會是 95%),和上面舉的三個主觀判斷條件對比一下差異:
- 不能 100% 確定該變量永遠不會被重新賦值(可能只是現在是沒被重新賦值,說不定未來會),或者
- 該變量的初始值不是字面量,比如說是個函數調用表達式,或者
- 該變量是個局部變量
第一種用法是非常主觀的,比如你用 const 的判斷條件有可能就和我上面舉的不一樣,比如你也許覺得局部變量也可以用 const ?或者你覺的數組和對象是可變的值,怎么能用 const 呢?因為很主觀,所以一個團隊的代碼風格很難達成一致,甚至只有你一個人維護的代碼,const 的用法在不同的時間內也沒有統一的規律,因為可能連你自己都沒有明確的想過:“什么時候我應該用 const”,說白了就是“看心情”。而第二種用法是非常客觀的,簡單直接,沒有含糊不清的地方,如果采用這種用法,至少團隊代碼風格的統一是不成問題的。
ES6 之前你有沒有寫過或者見過類似下面這樣的代碼:
var ids = document.getElementById("ids").value // 多個 id 組成的字符串,比如 "100 101 102" ids = ids.split(" ") // 拆分成數組 ids.forEach(...
ids 這個變量的類型從字符串變成了數組,但變量名卻沒變(寫代碼的人懶的想新名字),這被認為是一種不好的編碼風格。如果有“默認都用 const”的風格約定(第一行已經寫了 const ids),會迫使寫代碼的人在第二行想一個新的變量名(當然他也有可能把一行的 const 改成 let),而默認用 let 的風格不會讓人有種迫使感(當然用 let 的人也可能根本不需要迫使感就會主動想一個新的名字,看人)。
除了上面說的這兩個“默認用 const” 的優點,還有人說默認用 const 比用 let 能讓代碼更有可讀性,更語義:讀代碼的人看一個變量的聲明方式就知道它在下面會不會被重新賦值。但這些都是從代碼風格上說事的,代碼風格是很主觀的一個東西,有人覺的好,有人并不覺的不好,你很難拿著這樣的優點去說服默認用 let 的人。比如他們可能會回擊說:“我們團隊有詳細的風格指南,規定了什么時候用 const,代碼風格統一不是問題”(第一個優點沒了);“同樣是有風格指南,我們團隊的人不會復用不該復用的變量名”(第二個優點沒了);“let 比 const 短”(你反而無言以對,被擊敗)。
有沒有更有說服力的優點呢,比如能不能舉個“默認用 const 能夠避免,而默認用 let 避免不了的 bug” 的代碼實例呢?下面我構思了一個:
let path = require("path") // 這里 path 是個全局變量 /* 這里有很多代碼 */ function bar() { let path = "." // 這里 path 是個局部變量 /* 這里有很多代碼 */ if (this.isInProduction) { // 只在生產環境中執行的代碼 path = "~" // 為局部變量 path 重新賦值 } /* 使用局部變量 path 的代碼 */ }
假設這是一段運行正常的代碼, path 是個全局變量,同時又是個 bar 函數的局部變量。某天有人重構 bar 函數的時候認為 path 這個局部變量起的不好,和全局變量沖突了,應該換了個名字,然后把 bar 函數里面聲明 path 的地方和使用 path 的地方的 path 都替換成了 filePath,但不小心漏掉了 path = "~" 這一行里的 path,本地測試沒問題,上線出了故障,好心辦了壞事啊。
如果這個全局變量 path 是用 const 定義的話。。。也并不能發現這個 bug。為什么呢,請看我的上篇文章,是因為關于 const,ES6 有個設計缺陷,就是為 const 變量重新賦值不是個靜態錯誤。但是如果你同時使用了 ESLint 并開啟了 no-const-assign 規則,這個線上問題就能被扼殺在萌芽狀態:
總結:默認用 const 可以帶來一些代碼風格上的好處,如果配合 Lint 工具還可以避免某些意外的 bug,但如果不使用 Lint 工具,相反可能會造成一些意外的 bug(比如上篇文章中開頭舉的)。不管是可能造成的 bug,還是可能避免的 bug,都是些 edge case,并不常見,所以很大程度上,選擇默認用 const 還是 let 仍是一件很主觀的事。
ES6 的編輯 Allen Wirfs-Brock 曾說過,如果能重來一次,他希望把 let 設計成像現在的 const 一樣,而新增一個比如 let var 代替現在的 let。但因為 const 這個保留字在 JS 剛發明的時候就從 Java 抄過來了,let/const 這對兒關鍵字在制定 ES5 的時候就計劃在 ES6 里用了,所以 ES6 里也就沒再改了。
文章列表