1. 程式人生 > >走進EC6的let和const命令

走進EC6的let和const命令

defined hello erro -s add his win docs 方法

詳細學習鏈接: http://es6.ruanyifeng.com/#docs/let

let命令

基本用法

ES6新增了let命令,用來聲明變量。它的用法類似於var,但是所聲明的變量,只在let命令所在的代碼塊內有效。

‘use strict‘;
{
  let a = 10;
  var b = 1;
}

a // 報錯,ReferenceError: a is not defined.
b // 1

不存在變量提升

let不像var那樣會發生“變量提升”現象。所以,變量一定要在聲明後使用,否則報錯。

‘use strict‘;
console.log(foo); // 輸出undefined
console.log(bar); // 報錯,ReferenceError: bar is not defined.

var foo = 2;
let bar = 2;

暫時性死區

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

ES6明確規定,如果區塊中存在let和const命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。

總之,在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱TDZ)。

‘use strict‘;
if (true) {
    // TDZ開始
    tmp = ‘abc‘; // 報錯,ReferenceError: tmp is not defined.
    console.log(tmp); // 報錯,ReferenceError: tmp is not defined.

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

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

不允許重復聲明

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

‘use strict‘;
// 報錯,TypeError: Duplicate declaration "a".
function test() {
    let a = 10;
    var a = 1;
}

// 報錯,TypeError: Duplicate declaration "a".
function test() {
    let a = 10;
    let a = 1;
}

因此,不能在函數內部重新聲明參數。

‘use strict‘;
function func(arg) {
  let arg; // 報錯,TypeError: Duplicate declaration "arg"
}

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

塊級作用域

為什麽需要塊級作用域?

ES5只有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合理的場景。

第一種場景,內層變量可能會覆蓋外層變量。

‘use strict‘;
var tmp = new Date();

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

f() // undefined

上面代碼中,函數f執行後,輸出結果為undefined,原因在於變量提升,導致內層的tmp變量覆蓋了外層的tmp變量。

第二種場景,用來計數的循環變量泄露為全局變量。

‘use strict‘;
var s = ‘hello‘;

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

console.log(i); // 5

上面代碼中,變量i只用來控制循環,但是循環結束後,它並沒有消失,泄露成了全局變量。

ES6的塊級作用域

let實際上為JavaScript新增了塊級作用域。

‘use strict‘;
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

上面的函數有兩個代碼塊,都聲明了變量n,運行後輸出5。這表示外層代碼塊不受內層代碼塊的影響。如果使用var定義變量n,最後輸出的值就是10。

允許塊級作用域的任意嵌套

‘use strict‘;
{{{{{
    let insane = ‘Hello World‘
}}}}}

外層作用域無法讀取內層作用域的變量

‘use strict‘;
{{{{
  {
    let insane = ‘Hello World‘
  }
  console.log(insane); // 報錯,ReferenceError: insane is not defined.
}}}}

內層作用域可以定義外層作用域的同名變量

‘use strict‘;
{{{{
    let insane = ‘outside‘;
    {
        let insane = ‘inside‘;
        console.log(insane); // inside
    }
    console.log(insane); // outside
}}}}

取代立即執行匿名函數(IIFE)

‘use strict‘;
// IIFE寫法
(function () {
    var tmp = ‘hello world!‘;
}());
console.log(tmp); // 報錯,ReferenceError: tmp is not defined.

// 塊級作用域寫法
{
    let tmp = ‘hello world!‘;
}
console.log(tmp); // 報錯,ReferenceError: tmp is not defined.

函數本身的作用域,在其所在的塊級作用域之內

‘use strict‘;
function f() {
    console.log(‘outside‘);
}
(function () {
    if(false) {
        // 重復聲明一次函數f
        function f() {
            console.log(‘inside‘);
        }
    }

    f();
}());

上面代碼在ES5中運行,會得到“inside”,但是在ES6中運行,會得到“outside!”。這是因為ES5存在函數提升,不管會不會進入 if代碼塊,函數聲明都會提升到當前作用域的頂部,得到執行;

而ES6支持塊級作用域,不管會不會進入if代碼塊,其內部聲明的函數皆不會影響到作用域的外部。

塊級作用域外部,無法調用塊級作用域內部定義的函數

‘use strict‘;
let f;
{
    let a = ‘secret‘;
    let b = ‘publish‘;
    f = function () {
        return a;
    };

    function p() {
        return b;
    }
}
f(); // "secret"
p(); // 報錯,ReferenceError: p is not defined.

其它

ES5的嚴格模式規定,函數只能在頂層作用域和函數內聲明,其他情況(比如if代碼塊、循環代碼塊)的聲明都會報錯。

ES6由於引入了塊級作用域,這種情況可以理解成函數在塊級作用域內聲明,因此不報錯,但是構成區塊的大括號不能少,否則還是會報錯。

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

// ES6
// 不報錯
‘use strict‘;
if (true) {
  function f() {}
}

// 報錯,SyntaxError: Unexpected token.
‘use strict‘;
if (true)
  function f() {}

const命令

聲明只讀的常量

‘use strict‘;
const PI = 3.1415;
PI // 3.1415
PI = 3; // 報錯,TypeError: "PI" is read-only.

聲明時必須立即初始化

‘use strict‘;
const foo; // 報錯,SyntaxError: missing = in const declaration.

與let命令相同

  • 只在聲明所在的塊級作用域內有效
  • 聲明不提升
  • 存在暫時性死區,只能在聲明的位置後面使用
  • 不可重復聲明

只保證變量名指向的地址不變,並不保證該地址的數據不變

對於復合類型的變量,變量名不指向數據,而是指向數據所在的地址。const命令只是保證變量名指向的地址不變,並不保證該地址的數據不變,所以將一個對象聲明為常量必須非常小心。

‘use strict‘;
const foo = {};
foo.prop = 123;
foo.prop // 123
foo = {} // TypeError: "foo" is read-only

上面代碼中,常量foo儲存的是一個地址,這個地址指向一個對象。不可變的只是這個地址,即不能把foo指向另一個地址,但對象本身是可變的,所以依然可以為其添加新屬性。

跨模塊常量

const聲明的常量只在當前代碼塊有效。如果想設置跨模塊的常量,可以采用下面的寫法。

// constants.js 模塊
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模塊
import * as constants from ‘./constants‘;
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模塊
import {A, B} from ‘./constants‘;
console.log(A); // 1
console.log(B); // 3

其它

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

全局對象的屬性

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

window.a = 1;
a // 1

a = 2;
window.a // 2

上面代碼中,全局對象的屬性賦值與全局變量的賦值,是同一件事。(對於Node來說,這一條只對REPL環境適用,模塊環境之中,全局變量必須顯式聲明成global對象的屬性。)

這種規定被視為JavaScript語言的一大問題,因為很容易不知不覺就創建了全局變量。

ES6為了改變這一點,一方面規定,var命令和function命令聲明的全局變量,依舊是全局對象的屬性;另一方面規定,let命令、const命令、class命令聲明的全局變量,不屬於全局對象的屬性。

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

let b = 1;
window.b // undefined

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

走進EC6的let和const命令