ECMAScript 6 入門——let和const命令

一、let和const命令

1. let命令

只在let所在的代碼塊內有效

  • 例子(識別var和let的作用域區別,for說明)
    for循環的計數器,就很合適使用let命令。

    for (let i = 0; i < 10; i++) {
    // …
    }
    console.log(i);
    // ReferenceError: i is not defined

下面的代碼如果使用var,最後輸出的是10。

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

如果使用let,聲明的變量僅在塊級作用域內有效,最後輸出的是 6。

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

for循環還有一個特別之處,就是設置循環變量的那部分是一個父作用域,而循環體內部是一個單獨的子作用域。

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

ES6變量定義不存在變量提升

var在腳本開始運行時變量就存在,在定義前使用變量則undefined,這就是變量提升。
let則不會發生變量提升,變量聲明前不存在,使用則報錯。

// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;

// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;

暫時性死區

暫時性死區的本質就是,只要一進入當前作用域,所要使用的變量就已經存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現,纔可以獲取和使用該變量。語法上,稱爲“暫時性死區”(temporal dead zone,簡稱 TDZ)

理解:
  1. 在作用域內於定義let/const變量前使用該變量則發生暫時性死區error報錯
  2. typeof直接使用則變量提升得到undefined。但如之後用let/const定義變量則報錯——typeof不再是百分比安全。
if (true) {
  // TDZ開始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ結束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

typeof x; // ReferenceError
let x;
typeof undeclared_variable // "undefined"

例子:
function bar(x = y, y = 2) {
  return [x, y];
}
bar(); // 報錯

// 不報錯
var x = x;
// 報錯
let x = x;
// ReferenceError: x is not defined

不允許重複聲明

顧名思義,let不允許在相同作用域內重複聲明同一變量

function func(arg) {
  let arg;
}
func() // 報錯

function func(arg) {
  {
    let arg;
  }
}
func() // 不報錯

2. 塊級作用域

爲什麼需要塊級作用域(ES5作用域的弊端)

ES5只有全局作用域和函數作用域,會發生(1)變量覆蓋(2)變量泄露爲全局

  1. 內層變量覆蓋外層變量
//if中的變量聲明導致發送了變量提升,打印的tmp覆蓋了外層的tmp變成undefined
var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined
  1. 循環中泄露爲全局變量
var s = 'hello';

for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}

console.log(i); // 5

ES6的塊級作用域

(1)外層代碼不受內層代碼影響,即無法讀取(2)內層可以定義和外層作用域同名

  1. 外層不受內層代碼影響
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}
  • 內層可以定義外層作用域
 {{{{
  let insane = 'Hello World';
  {let insane = 'Hello'}
}}}};

塊級作用域與函數聲明

  • 區別
  1. ES5只能在頂層作用域和函數作用域之中聲明,不能在塊級作用域聲明。但瀏覽器不遵守這個規定,爲了兼容以前舊代碼
  2. ES6引入塊級作用域,明確允許在塊級作用域之中聲明函數,類似let,在塊級作用域之外不可引用該函數
ES5:
function f() { console.log('I am outside!'); }

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

  f();
}());

ES6:
// 瀏覽器的 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規定瀏覽器可不遵守上面的規定,有自己的行爲方式
  1. 運行在塊級作用域內聲明函數
  2. 函數聲明類似於var,即會提升到全局作用域或函數作用域的頭部
  3. 同時,函數聲明還會提升到所在的塊級作用域的頭部
    所以上面ES6的例子報錯可以這麼翻譯(發生了變量提升):
// 瀏覽器的 ES6 環境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function
ES6的塊級作用域必須有大括號,沒有則不存在塊級作用域
// 第一種寫法,報錯
if (true) let x = 1;

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

// 報錯
'use strict';
if (true)
  function f() {}

3. const命令

const實際上保證的,並不是變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。
(1)簡單類型數值/字符串/布爾值而言就等於常量,但對於複合類型的數據對象/數組,保存的只是一個指向實際數據的指針
(2)const聲明一個只讀的常量,一旦聲明必須初始化
(3)和let一樣是塊級作用域。其他也要,不可提升/同樣存在暫時性死區

(2)
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
(3)
const foo;
// SyntaxError: Missing initializer in const declaration
(1)
const foo = {};
// 爲 foo 添加一個屬性,可以成功
foo.prop = 123;
foo.prop // 123
// 將 foo 指向另一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only

const a = [];
a.push('Hello'); // 可執行
a.length = 0;    // 可執行
a = ['Dave'];    // 報錯

如果真想把對象變成常量,可以使用freeze進行凍結。之後的對象及其屬性都不可改變,嚴格模式下還會報錯

const foo = Object.freeze({});
// 常規模式時,下面一行不起作用;
// 嚴格模式時,該行會報錯
foo.prop = 123;

//連對象的屬性也凍結
var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

ES6聲明變量的六種方法

ES5:var和function
ES6:var、function、let、const、import、class

4. 頂層對象的屬性

頂層對象:(1)瀏覽器環境——window對象(2)Node環境指的是global對象

  • ES5
    (1)頂層對象的屬性window和全局變量var的賦值是同一件事。
    (2)頂層對象的屬性是隨處可寫,容易不知不覺創建全局變量var。
    問題:一不利於模塊化編程,二頂層對象有實體含義指定是瀏覽器的窗口對象window
window.a = 1;
a // 1

a = 2;
window.a // 2
  • ES6
    (1)爲保持兼容var和function命令聲明的全局變量依舊是頂層對象的屬性
    (2)let、const、class命令聲明的全局變量不屬於頂層對象window的屬性
var a = 1;
// 如果在 Node 的 REPL 環境,可以寫成 global.a
// 或者採用通用方法,寫成 this.a
window.a // 1

let b = 1;
window.b // undefined

5. globalThis對象

JavaScript 語言存在一個頂層對象,它提供全局環境(即全局作用域),所有代碼都是在這個環境中運行。

  • 頂層對象在各種實現中是不統一的:
    self:瀏覽器、Web Worker
    window:瀏覽器
    global:Node

  • 同樣的代碼爲能在各種環境都去到頂層對象,一般是使用this變量。但存在侷限性:
    (1)全局環境中,this會返回頂層對象。但Node、ES6模塊中,this返回的是當前模塊
    (2)函數中的this不作爲對象的方法運行,單純爲function則會指向頂層對象,但嚴格模式下會指向undefined
    (3)不管是嚴格模式,還是普通模式,new Function(‘return this’)(),總是會返回全局對象。但是,如果瀏覽器用了 CSP(Content Security Policy,內容安全策略),那麼eval、new Function這些方法都可能無法使用。

墊片庫global-this,可以在所有環境拿到globalThis

https://github.com/ungap/global-this

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