1 let
ES6 新增了let命令白粉,用來(lái)聲明變量传泊。它的用法類似于var,但是所聲明的變量鸭巴,只在let命令所在的代碼塊內(nèi)有效眷细。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
另外,for循環(huán)還有一個(gè)特別之處鹃祖,就是設(shè)置循環(huán)變量的那部分是一個(gè)父作用域溪椎,而循環(huán)體內(nèi)部是一個(gè)單獨(dú)的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
不存在變量提升
let命令改變了語(yǔ)法行為惯豆,它所聲明的變量一定要在聲明后使用池磁,否則報(bào)錯(cuò)。
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的情況
console.log(bar); // 報(bào)錯(cuò)ReferenceError
let bar = 2;
暫時(shí)性死區(qū)
只要塊級(jí)作用域內(nèi)存在let命令楷兽,它所聲明的變量就“綁定”(binding)這個(gè)區(qū)域地熄,不再受外部的影響。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代碼中芯杀,存在全局變量tmp端考,但是塊級(jí)作用域內(nèi)let又聲明了一個(gè)局部變量tmp雅潭,導(dǎo)致后者綁定這個(gè)塊級(jí)作用域,所以在let聲明變量前却特,對(duì)tmp賦值會(huì)報(bào)錯(cuò)扶供。
ES6明確規(guī)定,如果區(qū)塊中存在let和const命令裂明,這個(gè)區(qū)塊對(duì)這些命令聲明的變量椿浓,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量闽晦,就會(huì)報(bào)錯(cuò)扳碍。
總之,在代碼塊內(nèi)仙蛉,使用let命令聲明變量之前笋敞,該變量都是不可用的。這在語(yǔ)法上荠瘪,稱為“暫時(shí)性死區(qū)”(temporal dead zone夯巷,簡(jiǎn)稱 TDZ)。
“暫時(shí)性死區(qū)”也意味著typeof不再是一個(gè)百分之百安全的操作哀墓。
總之趁餐,暫時(shí)性死區(qū)的本質(zhì)就是,只要一進(jìn)入當(dāng)前作用域麸祷,所要使用的變量就已經(jīng)存在了澎怒,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn)阶牍,才可以獲取和使用該變量。
不允許重復(fù)聲明
let不允許在相同作用域內(nèi)星瘾,重復(fù)聲明同一個(gè)變量走孽。
因此,不能在函數(shù)內(nèi)部重新聲明參數(shù)琳状。
function func(arg) {
let arg; // 報(bào)錯(cuò)
}
function func(arg) {
{
let arg; // 不報(bào)錯(cuò)
}
}
2 塊級(jí)作用域
ES5 只有全局作用域和函數(shù)作用域磕瓷,沒有塊級(jí)作用域,這帶來(lái)很多不合理的場(chǎng)景念逞。
第一種場(chǎng)景困食,內(nèi)層變量可能會(huì)覆蓋外層變量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上面代碼的原意是翎承,if代碼塊的外部使用外層的tmp變量硕盹,內(nèi)部使用內(nèi)層的tmp變量。但是叨咖,函數(shù)f執(zhí)行后瘩例,輸出結(jié)果為undefined啊胶,原因在于變量提升,導(dǎo)致內(nèi)層的tmp變量覆蓋了外層的tmp變量垛贤。
ES6 的塊級(jí)作用域
let實(shí)際上為 JavaScript 新增了塊級(jí)作用域焰坪。
function f1() {
let n = 5;
if (true) {
let n = 10;
console.log(n); //10
}
console.log(n); // 5
}
塊級(jí)作用域不能向父作用域讀取同名變量,塊級(jí)作用域的出現(xiàn),實(shí)際上使得獲得廣泛應(yīng)用的立即執(zhí)行函數(shù)表達(dá)式(IIFE)不再必要了聘惦。
// IIFE 寫法
(function () {
var tmp = ...;
...
}());
// 塊級(jí)作用域?qū)懛?{
let tmp = ...;
...
}
塊級(jí)作用域與函數(shù)聲明
ES5 規(guī)定某饰,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明,不能在塊級(jí)作用域聲明善绎。
但是ES6 引入了塊級(jí)作用域黔漂,明確允許在塊級(jí)作用域之中聲明函數(shù)。ES6 規(guī)定涂邀,塊級(jí)作用域之中瘟仿,函數(shù)聲明語(yǔ)句的行為類似于let,在塊級(jí)作用域之外不可引用比勉。
ES6在[附錄B]里面規(guī)定劳较,瀏覽器的實(shí)現(xiàn)可以不遵守上面的規(guī)定,有自己的[行為方式]
允許在塊級(jí)作用域內(nèi)聲明函數(shù)浩聋。
函數(shù)聲明類似于var观蜗,即會(huì)提升到全局作用域或函數(shù)作用域的頭部。
同時(shí)衣洁,函數(shù)聲明還會(huì)提升到所在的塊級(jí)作用域的頭部墓捻。
注意,上面三條規(guī)則只對(duì) ES6 的瀏覽器實(shí)現(xiàn)有效坊夫,其他環(huán)境的實(shí)現(xiàn)不用遵守砖第,還是將塊級(jí)作用域的函數(shù)聲明當(dāng)作let處理。
根據(jù)這三條規(guī)則环凿,在瀏覽器的 ES6 環(huán)境中梧兼,塊級(jí)作用域內(nèi)聲明的函數(shù),行為類似于var聲明的變量智听。
考慮到環(huán)境導(dǎo)致的行為差異太大羽杰,應(yīng)該避免在塊級(jí)作用域內(nèi)聲明函數(shù)。如果確實(shí)需要到推,也應(yīng)該寫成函數(shù)表達(dá)式考赛,而不是函數(shù)聲明語(yǔ)句。
do 表達(dá)式
本質(zhì)上莉测,塊級(jí)作用域是一個(gè)語(yǔ)句颜骤,將多個(gè)操作封裝在一起,沒有返回值悔雹。
{
let t = f();
t = t * t + 1;
}
上面代碼中复哆,塊級(jí)作用域?qū)蓚€(gè)語(yǔ)句封裝在一起欣喧。但是,在塊級(jí)作用域以外梯找,沒有辦法得到t
的值唆阿,因?yàn)閴K級(jí)作用域不返回值,除非t
是全局變量锈锤。
現(xiàn)在有一個(gè)[提案]驯鳖,使得塊級(jí)作用域可以變?yōu)楸磉_(dá)式,也就是說(shuō)可以返回值久免,辦法就是在塊級(jí)作用域之前加上do浅辙,使它變?yōu)閐o表達(dá)式。
let x = do {
let t = f();
t * t + 1;
};
3 const 命令
const聲明一個(gè)只讀的常量阎姥。一旦聲明记舆,常量的值就不能改變。
const聲明的變量不得改變值呼巴,這意味著泽腮,const一旦聲明變量,就必須立即初始化衣赶,不能留到以后賦值诊赊。
對(duì)于const來(lái)說(shuō),只聲明不賦值府瞄,就會(huì)報(bào)錯(cuò)碧磅。
const的作用域與let命令相同:只在聲明所在的塊級(jí)作用域內(nèi)有效。
const命令聲明的常量也是不提升遵馆,同樣存在暫時(shí)性死區(qū)鲸郊,只能在聲明的位置后面使用。
const聲明的常量货邓,也與let一樣不可重復(fù)聲明严望。
本質(zhì):const實(shí)際上保證的,并不是變量的值不得改動(dòng)逻恐,而是變量指向的那個(gè)內(nèi)存地址不得改動(dòng)。對(duì)于簡(jiǎn)單類型的數(shù)據(jù)(數(shù)值峻黍、字符串复隆、布爾值),值就保存在變量指向的那個(gè)內(nèi)存地址姆涩,因此等同于常量挽拂。但對(duì)于復(fù)合類型的數(shù)據(jù)(主要是對(duì)象和數(shù)組),變量指向的內(nèi)存地址骨饿,保存的只是一個(gè)指針亏栈,const只能保證這個(gè)指針是固定的台腥,至于它指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的,就完全不能控制了绒北。因此黎侈,將一個(gè)對(duì)象聲明為常量必須非常小心。
例如:給const申明的變量賦值為一個(gè)空對(duì)象,對(duì)象地址不能改變,但可以利用對(duì)象的動(dòng)態(tài)屬性給對(duì)象添加屬性,const變量的值也就變相改變了
如果真的想將對(duì)象凍結(jié)闷游,應(yīng)該使用Object.freeze方法峻汉。
const foo = Object.freeze({});
// 常規(guī)模式時(shí),下面一行不起作用脐往;
// 嚴(yán)格模式時(shí)休吠,該行會(huì)報(bào)錯(cuò)
foo.prop = 123;
除了將對(duì)象本身凍結(jié),對(duì)象的屬性也應(yīng)該凍結(jié)业簿。下面是一個(gè)將對(duì)象徹底凍結(jié)的函數(shù)瘤礁。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
頂層對(duì)象的屬性
ES6為了改變這一點(diǎn),一方面規(guī)定梅尤,為了保持兼容性柜思,var命令和function命令聲明的全局變量,依舊是頂層對(duì)象的屬性克饶;另一方面規(guī)定酝蜒,let命令、const命令矾湃、class命令聲明的全局變量亡脑,不屬于頂層對(duì)象的屬性。也就是說(shuō)邀跃,從ES6開始霉咨,全局變量將逐步與頂層對(duì)象的屬性脫鉤。
var a = 1;
// 如果在Node的REPL環(huán)境拍屑,可以寫成global.a
// 或者采用通用方法途戒,寫成this.a
window.a // 1
let b = 1;
window.b // undefined
global 對(duì)象
ES5 的頂層對(duì)象,本身也是一個(gè)問題僵驰,因?yàn)樗诟鞣N實(shí)現(xiàn)里面是不統(tǒng)一的喷斋。
1 瀏覽器里面,頂層對(duì)象是window蒜茴,但 Node 和 Web Worker 沒有window星爪。
2 瀏覽器和 Web Worker 里面,self也指向頂層對(duì)象粉私,但是 Node 沒有self顽腾。
3 Node 里面,頂層對(duì)象是global诺核,但其他環(huán)境都不支持抄肖。
綜上所述久信,很難找到一種方法,可以在所有情況下漓摩,都取到頂層對(duì)象裙士。下面是兩種勉強(qiáng)可以使用的方法:
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
墊片庫(kù)system.global
模擬了這個(gè)提案,可以在所有環(huán)境拿到global幌甘。