ECMAScript6官方文檔學習筆記(一)----let和const命令

let命令

ES6 新增了let命令,用來聲明變量。它的用法類似於var,但是所聲明的變量,只在let命令所在的代碼塊內有效。
for循環的計數器,就很合適使用let命令。

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

輸出10,因爲這裏i是全局變量,所有數組a的成員裏面的i,指向的都是同一個i,導致運行時輸出的是最後一輪的i的值,也就是 10

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

輸出6,因爲申明的變量只在塊級作用域內有效
for循環還有一個特別之處,就是設置循環變量的那部分是一個父作用域,而循環體內部是一個單獨的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面代碼正確運行,輸出了 3 次abc。這表明函數內部的變量i與循環變量i不在同一個作用域,有各自單獨的作用域。
同時let申明的變量不存在變量提升

暫時性死區
只要塊級作用域內存在let命令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響。

不允許重複申明
let不允許在相同作用域內,重複申明同一個變量

塊級作用域
let實際上爲 JavaScript 新增了塊級作用域。
爲什麼需要塊級作用域?
ES5中只有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合理的場景
第一種場景,內部變量可能會覆蓋外部變量
第二種場景,用於計數的循環變量泄漏爲全局變量
塊級作用域的出現,實際上使得獲得廣泛應用的匿名立即執行函數表達式(匿名 IIFE)不再必要了。

// IIFE 寫法
(function () {
  var tmp = ...;
  ...
}());

// 塊級作用域寫法
{
  let tmp = ...;
  ...
}

塊級作用域和函數申明
ES5規定,函數只能在頂層作用域和函數作用域之中聲明,不能在塊級作用域聲明
ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數。ES6 規定,塊級作用域之中,函數聲明語句的行爲類似於let,在塊級作用域之外不可引用。
下面提出一個要注意的點:

// 瀏覽器的 ES6 環境
function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重複聲明一次函數f
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

ES6中理論上是應該得到“I am outside",原因上面咱們已經說了,但實際上會報錯,顯示f不是一個方法

原來,如果改變了塊級作用域內聲明的函數的處理規則,顯然會對老代碼產生很大影響。爲了減輕因此產生的不兼容問題,ES6 在附錄 B裏面規定,瀏覽器的實現可以不遵守上面的規定,有自己的行爲方式。

允許在塊級作用域內聲明函數。
函數聲明類似於var,即會提升到全局作用域或函數作用域的頭部。
同時,函數聲明還會提升到所在的塊級作用域的頭部。
注意,上面三條規則只對 ES6 的瀏覽器實現有效,其他環境的實現不用遵守,還是將塊級作用域的函數聲明當作let處理。

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

// 塊級作用域內部的函數聲明語句,建議不要使用
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 塊級作用域內部,優先使用函數表達式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

還有一個需要注意的地方。ES6 的塊級作用域必須有大括號,如果沒有大括號,JavaScript 引擎就認爲不存在塊級作用域。

// 第一種寫法,報錯
if (true) let x = 1;

// 第二種寫法,不報錯
if (true) {
  let x = 1;
}

注意:嚴格模式下,函數只能聲明在作用域的頂層

const命令

const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
const聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須立即初始化,不能留到以後賦值。對於const而言,只聲明不賦值,就會報錯

const的作用域與let命令相同:只在聲明所在的塊級作用域內有效。
const命令聲明的常量也是不提升,同樣存在暫時性死區,只能在聲明的位置後面使用。
const聲明的常量,也與let一樣不可重複聲明。

本質
const實際上保證的,並不是變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,因此等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的(即總是指向另一個固定的地址),至於它指向的數據結構是不是可變的,就完全不能控制了。因此,將一個對象聲明爲常量必須非常小心。
也就是說const一個對象,允許我們對這個對象添加屬性,但是不允許我們將它指向一個另一個對象
如果真的想將對象凍結,應該使用Object.freeze方法。

const foo = Object.freeze({});

// 常規模式時,下面一行不起作用;
// 嚴格模式時,該行會報錯
foo.prop = 123;

上面代碼中,常量foo指向一個凍結的對象,所以添加新屬性不起作用,嚴格模式時還會報錯。

ES6聲明變量的六種方法
ES5 只有兩種聲明變量的方法:var命令和function命令。ES6 除了添加let和const命令,後面章節還會提到,另外兩種聲明變量的方法:import命令和class命令。所以,ES6 一共有 6 種聲明變量的方法。

頂層對象的屬性
頂層對象,在瀏覽器環境指的是window對象,在 Node 指的是global對象。ES5 之中,頂層對象的屬性與全局變量是等價的。

window.a = 1;
a // 1

a = 2;
window.a // 2

上面代碼中,頂層對象的屬性賦值與全局變量的賦值,是同一件事。

頂層對象的屬性與全局變量掛鉤,被認爲是 JavaScript 語言最大的設計敗筆之一。
ES6 爲了改變這一點,一方面規定,爲了保持兼容性,var命令和function命令聲明的全局變量,依舊是頂層對象的屬性;另一方面規定,let命令、const命令、class命令聲明的全局變量,不屬於頂層對象的屬性。也就是說,從 ES6 開始,全局變量將逐步與頂層對象的屬性脫鉤。

var a = 1;
// 如果在 Node 的 REPL 環境,可以寫成 global.a
// 或者採用通用方法,寫成 this.a
window.a // 1

let b = 1;
window.b // undefined

上面代碼中,全局變量a由var命令聲明,所以它是頂層對象的屬性;全局變量b由let命令聲明,所以它不是頂層對象的屬性,返回undefined。

globalThis對象
全局環境中,this會返回頂層對象。但是,Node 模塊和 ES6 模塊中,this返回的是當前模塊。
函數裏面的this,如果函數不是作爲對象的方法運行,而是單純作爲函數運行,this會指向頂層對象。但是,嚴格模式下,這時this會返回undefined。
不管是嚴格模式,還是普通模式,new Function(‘return this’)(),總是會返回全局對象。但是,如果瀏覽器用了 CSP(Content Security Policy,內容安全策略),那麼eval、new Function這些方法都可能無法使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章