ES6學習筆記——let和const

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)。

這個特性的出現,導致typeof操作不是百分之百安全的了。

# 現在
typeof x; // ReferenceError
let x;

# 之前
typeof x; // "undefined"
var x;

上面代碼中,變量x使用let命令聲明,所以在聲明之前,都屬于x的“死區(qū)”,只要用到該變量就會報錯。因此,typeof運行時就會拋出一個ReferenceError。

作為比較,如果一個變量根本沒有被聲明,使用typeof反而不會報錯。

ES6 規(guī)定暫時性死區(qū)和let、const語句不出現變量提升,主要是為了減少運行時錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行為。這樣的錯誤在 ES5 是很常見的,現在有了這種規(guī)定,避免此類錯誤就很容易了。

總之,暫時性死區(qū)的本質就是,只要一進入當前作用域,所要使用的變量就已經存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現,才可以獲取和使用該變量

不允許重復聲明

let不允許在相同作用域內,重復聲明同一個變量。

塊級作用域

let實際是位js新增了塊級作用域。

  • 允許塊級作用域的任意嵌套
  • 外層作用域無法讀取內層作用域的變量
  • 內層作用域可以定義外層作用域的同名變量
  • 塊級作用域的出現,實際上使得獲得廣泛應用的立即執(zhí)行函數表達式(IIFE)不再必要了

塊級作用域和函數聲明

ES5中,以下情況是非法的:

// 情況一
if (true) {
  function f() {}
}

// 情況二
try {
  function f() {}
} catch(e) {
  // ...
}

但是,瀏覽器沒有遵守這個規(guī)定,為了兼容以前的舊代碼,還是支持在塊級作用域之中聲明函數,因此上面兩種情況實際都能運行,不會報錯。

ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數。ES6 規(guī)定,塊級作用域之中,函數聲明語句的行為類似于let,在塊級作用域之外不可引用。

考慮到環(huán)境導致的行為差異太大,應該避免在塊級作用域內聲明函數。如果確實需要,也應該寫成函數表達式,而不是函數聲明語句。

// 函數聲明語句
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函數表達式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

另外,還有一個需要注意的地方。ES6 的塊級作用域允許聲明函數的規(guī)則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。

const

const聲明一個只讀的常量。一旦聲明,常量的值就不能改變了。const聲明的變量不得改變值,這意味著,const一旦聲明變量,就必須立即初始化,不能留到以后賦值,如果你只聲明而不賦值,就會發(fā)生錯誤。

const聲明的常量也存在暫時性死區(qū),只能在聲明的位置后面使用;也不能重復聲明。

本質

const實際上保證的,并不是變量的值不得改動,而是指向的內存地址不能改動。這邊就會涉及到說,把對象變成const的,那么只能保證指向對象的指針是不變的,至于它的數據結構是不是可變是不可以空數字的,所以要很小心。

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ī)模式下,只是不起作用。

除了將對象本身凍結,對象的屬性也應該凍結。下面是一個將對象徹底凍結的函數:

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開始,全局變量將逐步與頂層對象的屬性脫鉤。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容