-
1、let命令
- 作用域
let命令與var基本相似橱乱,只是let所聲明的變量只在let代碼塊內(nèi)有效粱甫。
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
// ReferenceError: i is not defined
因為變量使用let聲明茶宵,所以在for循環(huán)內(nèi)乌庶,i變量可以被訪問,但是for循環(huán)外蛤吓,i變量就不可以訪問会傲,所以最后一行代碼會報錯。
- 此外 淌山,for循環(huán)還有一個特別之處,就是設置循環(huán)變量的那部分是一個父作用域德绿,而循環(huán)體內(nèi)部是一個單獨的子作用域退渗。
rainbow=function () {
for (let i=1;i<3;i++){
let i='abc';
console.log(i);
}
};
rainbow();
上面代碼正確運行会油,輸出了3次abc翻翩。這表明函數(shù)內(nèi)部的變量i與循環(huán)變量i不在同一個作用域,有各自單獨的作用域胶征。
- let命令不存在變量提升
var命令會發(fā)生”變量提升“現(xiàn)象睛低,即變量可以在聲明之前使用暇昂,值為undefined伴嗡。這種現(xiàn)象多多少少是有些奇怪的急波,按照一般的邏輯,變量應該在聲明語句之后才可以使用瘪校。
為了糾正這種現(xiàn)象澄暮,let命令改變了語法行為,它所聲明的變量一定要在聲明后使用阱扬,否則報錯泣懊。
rainbow=function () {
// var 的情況
console.log(foo);// 輸出undefined
var foo=2;
// let 的情況
console.log(bar);// 報錯ReferenceError
let bar=2
};
rainbow();
上面代碼中,變量foo用var命令聲明麻惶,會發(fā)生變量提升馍刮,即腳本開始運行時,變量foo已經(jīng)存在了窃蹋,但是沒有值静稻,所以會輸出undefined。變量bar用let命令聲明押搪,不會發(fā)生變量提升垂谢。這表示在聲明它之前,變量bar是不存在的焚虱,這時如果用到它,就會拋出一個錯誤。
- 暫時性死區(qū)
只要塊級作用域內(nèi)存在let命令,它所聲明的變量就“綁定”(binding)這個區(qū)域饮亏,不再受外部的影響。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代碼中,存在全局變量tmp砰识,但是塊級作用域內(nèi)let又聲明了一個局部變量tmp,導致后者綁定這個塊級作用域越平,所以在let聲明變量前瀑粥,對tmp賦值會報錯避咆。
ES6明確規(guī)定,如果區(qū)塊中存在let和const命令,這個區(qū)塊對這些命令聲明的變量,從一開始就形成了封閉作用域剂府。凡是在聲明之前就使用這些變量,就會報錯湾笛。
總之,在代碼塊內(nèi),使用let命令聲明變量之前,該變量都是不可用的闰靴。這在語法上幅恋,稱為“暫時性死區(qū)”(temporal dead zone淑翼,簡稱 TDZ)诵盼。
if (true) {
// TDZ開始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ結束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
上面代碼中戒财,在let命令聲明變量tmp之前饮寞,都屬于變量tmp的“死區(qū)”。
“暫時性死區(qū)”也意味著typeof不再是一個百分之百安全的操作。
typeof x; // ReferenceError
let x;
上面代碼中咨油,變量x使用let命令聲明法瑟,所以在聲明之前氓扛,都屬于x的“死區(qū)”,只要用到該變量就會報錯。因此,typeof運行時就會拋出一個ReferenceError犯建。
- 不允許重復聲明
let不允許在相同作用域內(nèi)否彩,重復聲明同一個變量。
// 報錯
function () {
let a = 10;
var a = 1;
}
// 報錯
function () {
let a = 10;
let a = 1;
}
因此肌毅,不能在函數(shù)內(nèi)部重新聲明參數(shù)呜舒。
function func(arg) {
let arg; // 報錯
}
function func(arg) {
{
let arg; // 不報錯
}
}
-
2般婆、塊級作用域
ES5 只有全局作用域和函數(shù)作用域,沒有塊級作用域晋辆,這帶來很多不合理的場景宇整。
第一種場景,內(nèi)層變量可能會覆蓋外層變量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上面的函數(shù)有兩個代碼塊,都聲明了變量n忽你,運行后輸出5简逮。這表示外層代碼塊不受內(nèi)層代碼塊的影響蕉堰。如果兩次都使用var定義變量n羹奉,最后輸出的值才是10诀拭。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
-
塊級作用域與函數(shù)聲明
ES6 引入了塊級作用域筒占,明確允許在塊級作用域之中聲明函數(shù)奏窑。ES6 規(guī)定,塊級作用域之中纬朝,函數(shù)聲明語句的行為類似于let骄呼,在塊級作用域之外不可引用共苛。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重復聲明一次函數(shù)f
function f() { console.log('I am inside!'); }
}
f();
}());
上面代碼在 ES5 中運行,會得到“I am inside!”蜓萄,因為在if內(nèi)聲明的函數(shù)f會被提升到函數(shù)頭部隅茎,實際運行的代碼如下。
// ES5 環(huán)境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
ES6 就完全不一樣了嫉沽,理論上會得到“I am outside!”辟犀。因為塊級作用域內(nèi)聲明的函數(shù)類似于let
,對作用域之外沒有影響绸硕。但是混滔,如果你真的在 ES6 瀏覽器中運行一下上面的代碼糙申,是會報錯的,這是為什么呢宁舰?
原來喊括,如果改變了塊級作用域內(nèi)聲明的函數(shù)的處理規(guī)則凭需,顯然會對老代碼產(chǎn)生很大影響狸棍。為了減輕因此產(chǎn)生的不兼容問題瀏覽器的實現(xiàn)可以不遵守上面的規(guī)定胆胰,有自己的行為方式.
- 允許在塊級作用域內(nèi)聲明函數(shù)。
- 函數(shù)聲明類似于var垮斯,即會提升到全局作用域或函數(shù)作用域的頭部郎仆。
- 同時,函數(shù)聲明還會提升到所在的塊級作用域的頭部甚脉。
注意,上面三條規(guī)則只對 ES6 的瀏覽器實現(xiàn)有效铆农,其他環(huán)境的實現(xiàn)不用遵守牺氨,還是將塊級作用域的函數(shù)聲明當作let
處理狡耻。
根據(jù)這三條規(guī)則,在瀏覽器的 ES6 環(huán)境中猴凹,塊級作用域內(nèi)聲明的函數(shù)夷狰,行為類似于var
聲明的變量。
考慮到環(huán)境導致的行為差異太大郊霎,應該避免在塊級作用域內(nèi)聲明函數(shù)沼头。如果確實需要,也應該寫成函數(shù)表達式书劝,而不是函數(shù)聲明語句进倍。
// 函數(shù)聲明語句
{
let a = 'secret';
function f() {
return a;
}
}
// 函數(shù)表達式
{
let a = 'secret';
let f = function () {
return a;
};
}
另外,還有一個需要注意的地方购对。ES6 的塊級作用域允許聲明函數(shù)的規(guī)則猾昆,只在使用大括號的情況下成立,如果沒有使用大括號骡苞,就會報錯垂蜗。
// 不報錯
'use strict';
if (true) {
function f() {}
}
// 報錯
'use strict';
if (true)
function f() {}
- do表達式
本質(zhì)上,塊級作用域是一個語句解幽,將多個操作封裝在一起贴见,沒有返回值。
{
let t = f();
t = t * t + 1;
}
上面代碼中躲株,塊級作用域將兩個語句封裝在一起片部。但是,在塊級作用域以外徘溢,沒有辦法得到t的值吞琐,因為塊級作用域不返回值,除非t是全局變量然爆。
現(xiàn)在有一個提案站粟,使得塊級作用域可以變?yōu)楸磉_式,也就是說可以返回值曾雕,辦法就是在塊級作用域之前加上do奴烙,使它變?yōu)閐o表達式。
let x = do {
let t = f();
t * t + 1;
};
上面代碼中剖张,變量x會得到整個塊級作用域的返回值切诀。
-
3、const命令
const聲明一個只讀的常量搔弄。一旦聲明幅虑,常量的值就不能改變。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const聲明的變量不得改變值顾犹,這意味著倒庵,const一旦聲明變量褒墨,就必須立即初始化,不能留到以后賦值擎宝。
const foo;
// SyntaxError: Missing initializer in const declaration
const的作用域與let命令相同:只在聲明所在的塊級作用域內(nèi)有效郁妈。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const命令聲明的常量也是不提升,同樣存在暫時性死區(qū)绍申,只能在聲明的位置后面使用噩咪。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
上面代碼在常量MAX聲明之前就調(diào)用,結果報錯极阅。
const聲明的常量胃碾,也與let一樣不可重復聲明。
var message = "Hello!";
let age = 25;
// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;
const本質(zhì):
const實際上保證的涂屁,并不是變量的值不得改動书在,而是變量指向的那個內(nèi)存地址不得改動。對于簡單類型的數(shù)據(jù)(數(shù)值拆又、字符串儒旬、布爾值),值就保存在變量指向的那個內(nèi)存地址帖族,因此等同于常量栈源。但對于復合類型的數(shù)據(jù)(主要是對象和數(shù)組),變量指向的內(nèi)存地址竖般,保存的只是一個指針甚垦,const只能保證這個指針是固定的,至于它指向的數(shù)據(jù)結構是不是可變的涣雕,就完全不能控制了艰亮。因此,將一個對象聲明為常量必須非常小心挣郭。
const foo = {};
// 為 foo 添加一個屬性迄埃,可以成功
foo.prop = 123;
foo.prop // 123
// 將 foo 指向另一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only
上面代碼中兑障,常量foo儲存的是一個地址侄非,這個地址指向一個對象。不可變的只是這個地址流译,即不能把foo指向另一個地址逞怨,但對象本身是可變的,所以依然可以為其添加新屬性福澡。
const a = [];
a.push('Hello'); // 可執(zhí)行
a.length = 0; // 可執(zhí)行
a = ['Dave']; // 報錯
上面代碼中叠赦,常量a是一個數(shù)組,這個數(shù)組本身是可寫的革砸,但是如果將另一個數(shù)組賦值給a除秀,就會報錯窥翩。
如果真的想將對象凍結,應該使用Object.freeze方法鳞仙。
const foo = Object.freeze({});
// 常規(guī)模式時,下面一行不起作用笔时;
// 嚴格模式時棍好,該行會報錯
foo.prop = 123;
上面代碼中,常量foo指向一個凍結的對象允耿,所以添加新屬性不起作用借笙,嚴格模式時還會報錯。
除了將對象本身凍結较锡,對象的屬性也應該凍結业稼。下面是一個將對象徹底凍結的函數(shù)。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES6 聲明變量的六種方法:
var, function, let, const,import,class
-
4蚂蕴、ES6 聲明變量的六種方法
頂層對象低散,在瀏覽器環(huán)境指的是window對象,在Node指的是global對象骡楼。ES5之中熔号,頂層對象的屬性與全局變量是等價的。
window.a = 1;
a // 1
a = 2;
window.a // 2
ES6為了改變這一點鸟整,一方面規(guī)定引镊,為了保持兼容性,var命令和function命令聲明的全局變量篮条,依舊是頂層對象的屬性弟头;另一方面規(guī)定,let命令涉茧、const命令赴恨、class命令聲明的全局變量,不屬于頂層對象的屬性降瞳。也就是說嘱支,從ES6開始,全局變量將逐步與頂層對象的屬性脫鉤挣饥。
var a = 1;
// 如果在Node的REPL環(huán)境除师,可以寫成global.a
// 或者采用通用方法,寫成this.a
window.a // 1
let b = 1;
window.b // undefined
上面代碼中扔枫,全局變量a由var命令聲明汛聚,所以它是頂層對象的屬性;全局變量b由let命令聲明短荐,所以它不是頂層對象的屬性倚舀,返回undefined叹哭。