ES6新特性之let和const命令
1、let命令
ES6新增了let命令,用來宣告變數,它的用法類似于var,但是所宣告的變數,只在let命令所在的代碼塊內有效,
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
- 不存在變數提升
let不像var那樣會發生“變數提升”現象,所以,變數一定要在宣告后使用,否則報錯,
console.log(foo); // 輸出undefined
console.log(bar); // 報錯ReferenceError
var foo = 2;
let bar = 2;
上面代碼中,變數foo用var命令宣告,會發生變數提升,即腳本開始運行時,變數foo已經存在了,但是沒有值,所以會輸出undefined,變數bar用let命令宣告,不會發生變數提升,這表示在宣告它之前,變數bar是不存在的,這時如果用到它,就會拋出一個錯誤,
- 暫時性死區
只要塊級作用域記憶體在let命令,它所宣告的變數就“系結”(binding)這個區域,不再受外部的影響,
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代碼中,存在全域變數tmp,但是塊級作用域內let又宣告了一個區域變數tmp,導致后者系結這個塊級作用域,所以在let宣告變數前,對tmp賦值會報錯,
ES6明確規定,如果區塊中存在let和const命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉作用域,凡是在宣告之前就使用這些變數,就會報錯,
總之,在代碼塊內,使用let命令宣告變數之前,該變數都是不可用的,這在語法上,稱為“暫時性死區”(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的“死區”,
有些“死區”比較隱蔽,不太容易發現,
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 報錯
上面代碼中,呼叫bar函式之所以報錯(某些實作可能不報錯),是因為引數x默認值等于另一個引數y,而此時y還沒有宣告,屬于”死區“,如果y的默認值是x,就不會報錯,因為此時x已經宣告了,
ES6規定暫時性死區和let、const陳述句不出現變數提升,主要是為了減少運行時錯誤,防止在變數宣告前就使用這個變數,從而導致意料之外的行為,這樣的錯誤在ES5是很常見的,現在有了這種規定,避免此類錯誤就很容易了,
總之,暫時性死區的本質就是,只要一進入當前作用域,所要使用的變數就已經存在了,但是不可獲取,只有等到宣告變數的那一行代碼出現,才可以獲取和使用該變數,
- 不允許重復宣告
let不允許在相同作用域內,重復宣告同一個變數,
// 報錯
function () {
let a = 10;
var a = 1;
}
// 報錯
function () {
let a = 10;
let a = 1;
}
因此,不能在函式內部重新宣告引數,
function func(arg) {
let arg; // 報錯
}
function func(arg) {
{
let arg; // 不報錯
}
}
- 塊級作用域
為什么需要塊級作用域?
ES5只有全域作用域和函式作用域,沒有塊級作用域,這帶來很多不合理的場景,
第一種場景,內層變數可能會覆寫外層變數,
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = "hello world";
}
}
f(); // undefined
上面代碼中,函式f執行后,輸出結果為undefined,原因在于變數提升,導致內層的tmp變數覆寫了外層的tmp變數,
第二種場景,用來計數的回圈變數泄露為全域變數,
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代碼中,變數i只用來控制回圈,但是回圈結束后,它并沒有消失,泄露成了全域變數,
ES6的塊級作用域
let實際上為JavaScript新增了塊級作用域,
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
內層作用域可以定義外層作用域的同名變數,
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
塊級作用域的出現,實際上使得獲得廣泛應用的立即執行函式運算式(IIFE)不再必要了,
// IIFE 寫法
(function () {
var tmp = ...;
...
}());
// 塊級作用域寫法
{
let tmp = ...;
...
}
塊級作用域與函式宣告
函式能不能在塊級作用域之中宣告,是一個相當令人混淆的問題,
ES5規定,函式只能在頂層作用域和函式作用域之中宣告,不能在塊級作用域宣告,
// 情況一
if (true) {
function f() {}
}
// 情況二
try {
function f() {}
} catch(e) {
}
上面代碼的兩種函式宣告,根據ES5的規定都是非法的,
但是,瀏覽器沒有遵守這個規定,為了兼容以前的舊代碼,還是支持在塊級作用域之中宣告函式,因此上面兩種情況實際都能運行,不會報錯,不過,“嚴格模式”下還是會報錯,
// ES5嚴格模式
'use strict';
if (true) {
function f() {}
}
// 報錯
// ES6 引入了塊級作用域,明確允許在塊級作用域之中宣告函式,
// ES6嚴格模式
'use strict';
if (true) {
function f() {}
}
// 不報錯
//ES6 規定,塊級作用域之中,函式宣告陳述句的行為類似于let,在塊級作用域之外不可參考,
考慮到環境導致的行為差異太大,應該避免在塊級作用域內宣告函式,如果確實需要,也應該寫成函式運算式,而不是函式宣告陳述句,
// 函式宣告陳述句
{
let a = 'secret';
function f() {
return a;
}
}
// 函式運算式
{
let a = 'secret';
let f = function () {
return a;
};
}
另外,還有一個需要注意的地方,ES6的塊級作用域允許宣告函式的規則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯,
// 不報錯
'use strict';
if (true) {
function f() {}
}
// 報錯
'use strict';
if (true)
function f() {}
- do 運算式
本質上,塊級作用域是一個陳述句,將多個操作封裝在一起,沒有回傳值,
{
let t = f();
t = t * t + 1;
}
上面代碼中,塊級作用域將兩個陳述句封裝在一起,但是,在塊級作用域以外,沒有辦法得到t的值,因為塊級作用域不回傳值,除非t是全域變數,
現在有一個提案,使得塊級作用域可以變為運算式,也就是說可以回傳值,辦法就是在塊級作用域之前加上do,使它變為do運算式,
let x = do {
let t = f();
t * t + 1;
};
//上面代碼中,變數x會得到整個塊級作用域的回傳值,
2、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來說,只宣告不賦值,就會報錯,
const的作用域與let命令相同:只在宣告所在的塊級作用域內有效,
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const命令宣告的常量也是不提升,同樣存在暫時性死區,只能在宣告的位置后面使用,
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
上面代碼在常量MAX宣告之前就呼叫,結果報錯,
const宣告的常量,也與let一樣不可重復宣告,
var message = "Hello!";
let age = 25;
// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;
對于復合型別的變數,變數名不指向資料,而是指向資料所在的地址,const命令只是保證變數名指向的地址不變,并不保證該地址的資料不變,所以將一個物件宣告為常量必須非常小心,
const foo = {};
foo.prop = 123;
foo.prop
// 123
foo = {}; // TypeError: "foo" is read-only
上面代碼中,常量foo儲存的是一個地址,這個地址指向一個物件,不可變的只是這個地址,即不能把foo指向另一個地址,但物件本身是可變的,所以依然可以為其添加新屬性,
const a = [];
a.push('Hello'); // 可執行
a.length = 0; // 可執行
a = ['Dave']; // 報錯
上面代碼中,常量a是一個陣列,這個陣列本身是可寫的,但是如果將另一個陣列賦值給a,就會報錯,
如果真的想將物件凍結,應該使用Object.freeze方法,
const foo = Object.freeze({});
// 常規模式時,下面一行不起作用;
// 嚴格模式時,該行會報錯
foo.prop = 123;
上面代碼中,常量foo指向一個凍結的物件,所以添加新屬性不起作用,嚴格模式時還會報錯,
除了將物件本身凍結,物件的屬性也應該凍結,下面是一個將物件徹底凍結的函式,
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, value) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES5只有兩種宣告變數的方法:var命令和function命令,ES6除了添加let和const命令,后面章節還會提到,另外兩種宣告變數的方法:import命令和class命令,所以,ES6一共有6種宣告變數的方法,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/257807.html
標籤:其他
下一篇:什么是mapbox?
