很早就想系統的學習ES6了,奈何資源太少,把阮一峰大大的ES6通讀了一遍之后發現,沒有一點感覺,是的,也許是自己的積累或者境界不夠,真的一點感覺都沒有,市面上可供學習參考的ES6的資料真的不多。之前一直盼望著《你不知道的JavaScript下卷》出中文版,聽說到11月份,好吧。一次偶然,得知《高程》的作者也寫了ES6的書,雖然中文版也沒有,但是真的忍不了的,用我的渣英語慢慢讀吧。
問在前面:
- 問:用
const
申明的對象是可以修改的,對嗎?
答:對。 - 用
let
申明的全局變量和用var
申明的全局變量是一樣的嗎?
答:不一樣。
如果你都回答上了,并且知其然知其所以然,那你掌握的很好,不用往下看了。如果并不知道,那就一起慢慢回顧一下吧。
var###
相信大家已經對let
,const
有了一定的認識,也應該了解了塊級作用域以及var
并沒有塊級作用域。
var
沒有塊級作用域,具體體現在兩個方面,一個是在函數中,一個是在循環中。具體例子如下:
var foo = function(a){
if(a){
var text = 'hello'
return text
}else{
//這里也有text,其值為undefined
return null
}
//這里也有text,其值為undefined
}
其實我們的本意是希望如下代碼:
var foo = function(a){
if(a){
var text = 'hello'
return text
}else{
//這里text未定義
return null
}
//這里text未定義
}
但是因為var
沒有塊級作用域,并且存在變量提升,所以上述代碼其實與下面的寫法相同:
var foo = function(a){
var text;undefined
if(a){
text = 'hello'
return text
}else{
//這里也有text,其值為undefined
return null
}
//這里也有text,其值為undefined
}
這是在函數中,其實在循環中也一樣,比如:
for (var i = 0; i < 10; i++) {
//做一些操作
}
//在外部,i依舊存在,并且會輸出10
console.log(i); //10
很明顯,這是不符合常理的,我們期望的是:
for (var i = 0; i < 10; i++) {
//做一些操作
}
//在外部,i未定義,報錯
console.log(i); //ReferenceError
然而并做不到。再來舉個例子,在循環中的函數,使用var
,是更加糟糕,更加不符合常理的,比如:
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function() { console.log(i); });
}
funcs.forEach(function(func) {
func(); // 輸出10,10次
});
其實我們期待的是,輸出0-9
,但是事與愿違,輸出了10次10
,為什么?因為for
循環中的每一次迭代都共享著用var
申明的i
,也就是說,這里的i
不是分別在單獨的作用域里,而是在同一個作用域里,是一個值,而不是十個,所以,一改全改,最終變成輸出10個10。
如何解決這個問題呢?
在ES5中,我們想到了用立即執行函數(IIFE),把每一個i
的作用域分開。具體代碼如下:
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
funcs.forEach(function(func) {
func(); //輸出0-9
});
解決了,但是顯得太臃腫而且難以理解是不是?是的,一眼看過去很難看清楚這個函數到底想表達什么。但是,如果我們一開始就使用let
代替var
,這個世界就變得簡單而清爽了:
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs.push(function() { console.log(i); });
}
funcs.forEach(function(func) {
func(); // 輸出0-9
});
符合邏輯,并且代碼清爽。完美!這就是let
的魅力。下面讓我們來深入學習let
。
let###
let
和var
差不多,只不過申明的變量有塊級作用域,并且不會變量提升。什么意思呢,就是說,let
聲明的變量只存在于聲明它的{}
內部,或者循環體內部,或者函數內部,外部是不存在的。因為沒有變量提升,所以有暫時性死區。
暫時性死區
這個概念我覺得是相對于var
來說的,因為var
有變量提升,所以先使用后申明是不會報錯的,而是undefined
,具體例子如下:
console.log(value);//undefined
var value = 10;
console.log(value);//10
其實這個就等于:
var value;
console.log(value);//undefined
value = 10;
console.log(value);//10
說起變量提升,在這里想多說一點。函數用function (){····}
這樣的方式,也有提升,而且是在變量提升之前,也就是說,提升的優先級比變量提升高,并且同名函數,后者會覆蓋前者。也就是說:
foo();//b
function foo(){
console.log('a')
}
function foo(){
console.log('b')
}
扯遠了,現在回到let
的暫時性死區,和var
不同,請看下面的例子:
console.log(value);//報錯,ReferenceError
let value = 10;
console.log(value);
因為沒有變量提升,所以只能先聲明,后使用。
還要一些小細節,在同一作用域中var
申明變量是可以重復申明的,并且后者覆蓋前者:
var a = 10;
var a = 20;
console.log(a)//20
而在let
中,這樣會報錯:
let a = 10;
let a = 20;//報錯,語法錯誤,a已經申明過了。
console.log(a)
當然,這樣也不行:
var a = 10;
let a = 20;//報錯,語法錯誤,a已經申明過了。
console.log(a)
不同作用域,是可以的:
var a = 10;
function foo(){
let a = 20;//合法
}
回到最初提出的問題之一,全局作用域下,let
和var
申明的變量的不同之處
首先,var
大家都了解,全局申明的變量在瀏覽器中就是window
的屬性,比如:
var a = 10;
window.a//10
給個最直觀的例子:
let a = 10;
window.a//報錯,a未定義
明顯可以看出,這是不同的。下面,我們來深入一點,用var
申明全局變量并沒有想象中的那么好,比如:
var RegExp = 10;
window.RegExp//10
是不是賊恐怖?把內置對象給干掉了,如此危險的操作,肯定是要避免的。用let
就可以避免了:
let RegExp = 10;
window.RegExp//function RegExp() { [native code] }
console.log(RegExp )//10
為什么會這樣?因為用var
申明的全局變量會當做window
對象的屬性,而let
申明的全局變量,只是一個變量,不會當做window
對象的屬性。let
就說的差不多了,下面說下const
。
const###
其實const
和let
用法,包括需要注意的一些細節都一樣比如沒有變量提升,聲明全局變量,塊級作用域。但是還是有一些地方是不一樣的,最大的一個不同點就是,const
申明的變量,是不可變的,變了就報錯,具體例子如下:
const a =10;
a = 20;//報錯,語法錯誤,不能改變一個不變的值
當然,如果你以為這就是const
的全部,那就不太好了,回到開頭提出的問題,如果用const
申明的是一個對象呢?
const a = {
value:10
}
a.value = 20;
a;//a{value:20}
修改了,沒有報錯。可能你有些動搖了,那么看下一個例子:
const a = {
value:10
}
a = {
value:20//報錯,語法錯誤,不能改變一個不變的值
}
結論,const
只會鎖定變量的地址,而不會鎖定變量的屬性,這個和Object.freeze()
,還是有很大的區別的。
總結###
-
var
沒有想象中的那么好,盡量使用let
,const
代替。 -
var
申明的全局變量,除非你真的是想給window
對象加屬性,不然就用let
,const
代替。 -
let
,const
沒有變量提升,注意暫時性死區。 -
const
并不是真正的鎖定變量的所有,而只是鎖定這個變量的地址而已,所以這個變量的屬性是可以修改的。 - 盡量都是用
const
,除非你確定你申明的是一個變量(會變的值)。