let和const命令
let
ES6新增了變量let來解決之前塊級作用域的問題蚯窥。let所聲明的變量只會在代碼塊內有效。
可以看一下下面這個例子:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
第一組代碼中的var是全局的仇穗,所有的i都指向一個i腮郊,所以會是10狱庇,但是在第二組代碼中宁赤。i由let聲明舀透,只在塊級作用域里有效,所以輸出了6决左。
對于for循環(huán)愕够,有特別之處,設置循環(huán)的那部分是父作用域佛猛,而循環(huán)體內部是單獨的子作用域惑芭。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
由代碼可知,兩個i不一樣
不存在變量提升
使用var會導致變量提升继找,變量在聲明前被使用遂跟,不會報錯,只會輸出undefined婴渡。但是使用let之后再聲明之前使用變量會直接報錯幻锁。
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;
暫時性死區(qū)(TDZ)
如果你在一個塊級作用域中使用了let,它所聲明的變量就在這個區(qū)域內不受外部的影響缩搅。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
在該塊級作用域中,tmp使用了let就被綁死了触幼,在該區(qū)域未聲明tmp之前使用tmp就會報錯硼瓣,而跟全局的tmp沒有什么關系。
ES6明確規(guī)定,如果區(qū)塊中存在let和const命令堂鲤,這個區(qū)塊對這些命令聲明的變量亿傅,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量瘟栖,就會報錯葵擎。
總之,在代碼塊內半哟,使用let命令聲明變量之前酬滤,該變量都是不可用的。這在語法上寓涨,稱為“暫時性死區(qū)”(temporal dead zone盯串,簡稱 TDZ)。
這個特性的出現(xiàn)戒良,導致typeof操作不是百分之百安全的了体捏。
# 現(xiàn)在
typeof x; // ReferenceError
let x;
# 之前
typeof x; // "undefined"
var x;
上面代碼中,變量x使用let命令聲明糯崎,所以在聲明之前几缭,都屬于x的“死區(qū)”,只要用到該變量就會報錯沃呢。因此年栓,typeof運行時就會拋出一個ReferenceError。
作為比較樟插,如果一個變量根本沒有被聲明韵洋,使用typeof反而不會報錯。
ES6 規(guī)定暫時性死區(qū)和let黄锤、const語句不出現(xiàn)變量提升搪缨,主要是為了減少運行時錯誤,防止在變量聲明前就使用這個變量鸵熟,從而導致意料之外的行為副编。這樣的錯誤在 ES5 是很常見的,現(xiàn)在有了這種規(guī)定流强,避免此類錯誤就很容易了痹届。
總之,暫時性死區(qū)的本質就是打月,只要一進入當前作用域队腐,所要使用的變量就已經存在了,但是不可獲取奏篙,只有等到聲明變量的那一行代碼出現(xiàn)柴淘,才可以獲取和使用該變量迫淹。
不允許重復聲明
let不允許在相同作用域內,重復聲明同一個變量为严。
塊級作用域
let實際是位js新增了塊級作用域敛熬。
- 允許塊級作用域的任意嵌套
- 外層作用域無法讀取內層作用域的變量
- 內層作用域可以定義外層作用域的同名變量
- 塊級作用域的出現(xiàn),實際上使得獲得廣泛應用的立即執(zhí)行函數(shù)表達式(IIFE)不再必要了
塊級作用域和函數(shù)聲明
ES5中第股,以下情況是非法的:
// 情況一
if (true) {
function f() {}
}
// 情況二
try {
function f() {}
} catch(e) {
// ...
}
但是应民,瀏覽器沒有遵守這個規(guī)定,為了兼容以前的舊代碼夕吻,還是支持在塊級作用域之中聲明函數(shù)诲锹,因此上面兩種情況實際都能運行,不會報錯梭冠。
ES6 引入了塊級作用域辕狰,明確允許在塊級作用域之中聲明函數(shù)。ES6 規(guī)定控漠,塊級作用域之中蔓倍,函數(shù)聲明語句的行為類似于let,在塊級作用域之外不可引用盐捷。
考慮到環(huán)境導致的行為差異太大偶翅,應該避免在塊級作用域內聲明函數(shù)。如果確實需要碉渡,也應該寫成函數(shù)表達式聚谁,而不是函數(shù)聲明語句。
// 函數(shù)聲明語句
{
let a = 'secret';
function f() {
return a;
}
}
// 函數(shù)表達式
{
let a = 'secret';
let f = function () {
return a;
};
}
另外滞诺,還有一個需要注意的地方形导。ES6 的塊級作用域允許聲明函數(shù)的規(guī)則,只在使用大括號的情況下成立习霹,如果沒有使用大括號朵耕,就會報錯。
const
const聲明一個只讀的常量淋叶。一旦聲明阎曹,常量的值就不能改變了。const聲明的變量不得改變值煞檩,這意味著处嫌,const一旦聲明變量,就必須立即初始化斟湃,不能留到以后賦值熏迹,如果你只聲明而不賦值,就會發(fā)生錯誤凝赛。
const聲明的常量也存在暫時性死區(qū)注暗,只能在聲明的位置后面使用厨剪;也不能重復聲明。
本質
const實際上保證的友存,并不是變量的值不得改動,而是指向的內存地址不能改動陶衅。這邊就會涉及到說屡立,把對象變成const的,那么只能保證指向對象的指針是不變的搀军,至于它的數(shù)據(jù)結構是不是可變是不可以空數(shù)字的膨俐,所以要很小心。
const foo = {};
// 為 foo 添加一個屬性罩句,可以成功
foo.prop = 123;
foo.prop // 123
// 將 foo 指向另一個對象焚刺,就會報錯
foo = {}; // TypeError: "foo" is read-only
const a = [];
a.push('Hello'); // 可執(zhí)行
a.length = 0; // 可執(zhí)行
a = ['Dave']; // 報錯
如果你真的想把對象鎖起來,可以使用Object.freeze方法门烂。但是只在嚴格模式下會報錯乳愉,常規(guī)模式下,只是不起作用屯远。
除了將對象本身凍結蔓姚,對象的屬性也應該凍結。下面是一個將對象徹底凍結的函數(shù):
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
頂層對象的屬性
頂層對象的屬性與全局變量掛鉤慨丐,被認為是JavaScript語言最大的設計敗筆之一坡脐。這樣的設計帶來了幾個很大的問題,首先是沒法在編譯時就報出變量未聲明的錯誤房揭,只有運行時才能知道(因為全局變量可能是頂層對象的屬性創(chuàng)造的备闲,而屬性的創(chuàng)造是動態(tài)的);其次捅暴,程序員很容易不知不覺地就創(chuàng)建了全局變量(比如打字出錯)恬砂;最后,頂層對象的屬性是到處可以讀寫的伶唯,這非常不利于模塊化編程觉既。另一方面,window對象有實體含義乳幸,指的是瀏覽器的窗口對象瞪讼,頂層對象是一個有實體含義的對象,也是不合適的粹断。
ES6為了改變這一點符欠,一方面規(guī)定,為了保持兼容性瓶埋,var命令和function命令聲明的全局變量希柿,依舊是頂層對象的屬性诊沪;另一方面規(guī)定,let命令曾撤、const命令端姚、class命令聲明的全局變量,不屬于頂層對象的屬性挤悉。也就是說渐裸,從ES6開始,全局變量將逐步與頂層對象的屬性脫鉤装悲。