ECMAScript 6 let和const命令


    自從nodejs升級到4.0後,已經開始支持ECMAScript 6了。。。
javascript

剛開始看阮一峯的ES6 入門教程,發現變量聲明多了兩個屬性,let和const。相對於原來單一的var,變得愈來愈像java了,引入了塊級做用域的概念,const是聲明常量的。js愈來愈像後臺語言了,也愈來愈嚴謹了。java

let和const命令


  1. let命令node

  2. 塊級做用域數組

  3. const命令瀏覽器

  4. 跨模塊常量安全

  5. 全局對象的屬性ide

let命令

基本用法

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

{
  let a = 10;
  var b = 1;}a // ReferenceError: a is not defined.b // 1

上面代碼在代碼塊之中,分別用letvar聲明瞭兩個變量。而後在代碼塊以外調用這兩個變量,結果let聲明的變量報錯,var聲明的變量返回了正確的值。這代表,let聲明的變量只在它所在的代碼塊有效。this

for循環的計數器,就很合適使用let命令。spa

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

上面代碼的計數器i,只在for循環體內有效。

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

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

上面代碼中,變量ivar聲明的,在全局範圍內都有效。因此每一次循環,新的i值都會覆蓋舊值,致使最後輸出的是最後一輪的i的值。

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

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

上面代碼中,變量ilet聲明的,當前的i只在本輪循環有效,因此每一次循環的i其實都是一個新的變量,因此最後輸出的是6。

不存在變量提高

let不像var那樣,會發生「變量提高」現象。因此,變量必定要在聲明後使用,不然報錯。

console.log(foo); // ReferenceErrorlet foo = 2;

上面代碼在聲明foo以前,就使用這個變量,結果會拋出一個錯誤。

這也意味着typeof再也不是一個百分之百安全的操做。

typeof x; // ReferenceErrorlet x;

上面代碼中,因爲typeof運行時,x尚未聲明,因此會拋出一個ReferenceError

暫時性死區

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

var tmp = 123;if (true) {
  tmp = 'abc'; // ReferenceError  let tmp;}

上面代碼中,存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定這個塊級做用域,因此在let聲明變量前,對tmp賦值會報錯。

ES6明確規定,若是區塊中存在letconst命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些命令,就會報錯。

總之,在代碼塊內,使用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已經聲明瞭。

function bar(x = 2, y = x) {
  return [x, y];}bar(); // [2, 2]

ES6規定暫時性死區和不存在變量提高,主要是爲了減小運行時錯誤,防止在變量聲明前就使用這個變量,從而致使意料以外的行爲。這樣的錯誤在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}

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

ES6容許塊級做用域的任意嵌套。

{{{{{let insane = 'Hello World'}}}}};

上面代碼使用了一個五層的塊級做用域。外層做用域沒法讀取內層做用域的變量。

{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 報錯}}}};

內層做用域能夠定義外層做用域的同名變量。

{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World';}}}}};

塊級做用域的出現,實際上使得得到普遍應用的當即執行匿名函數(IIFE)再也不必要了。

// IIFE寫法(function () {
  var tmp = ...;
  ...}());// 塊級做用域寫法{
  let tmp = ...;
  ...}

另外,ES6也規定,函數自己的做用域,在其所在的塊級做用域以內。

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

  f();}());

上面代碼在ES5中運行,會獲得「I am inside!」,可是在ES6中運行,會獲得「I am outside!」。這是由於ES5存在函數提高,無論會不會進入 if代碼塊,函數聲明都會提高到當前做用域的頂部,獲得執行;而ES6支持塊級做用域,無論會不會進入if代碼塊,其內部聲明的函數皆不會影響到做用域的外部。

{
  let a = 'secret';
  function f() {
    return a;
  }}f() // 報錯

上面代碼中,塊級做用域外部,沒法調用塊級做用域內部定義的函數。若是確實須要調用,就要像下面這樣處理。

let f;{
  let a = 'secret';
  f = function () {
    return a;
  }}f() // "secret"

須要注意的是,若是在嚴格模式下,函數只能在頂層做用域和函數內聲明,其餘狀況(好比if代碼塊、循環代碼塊)的聲明都會報錯。

const命令

const也用來聲明變量,可是聲明的是常量。一旦聲明,常量的值就不能改變。

const PI = 3.1415;PI // 3.1415PI = 3;// TypeError: "PI" is read-only

上面代碼代表改變常量的值會報錯。

const聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須當即初始化,不能留到之後賦值。

const foo;// SyntaxError: missing = 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// 123foo = {} // 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種聲明變量的方法。

跨模塊常量

上面說過,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); // 1console.log(constants.B); // 3// test2.js 模塊import {A, B} from './constants';console.log(A); // 1console.log(B); // 3

全局對象的屬性

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

window.a = 1;a // 1a = 2;window.a // 2

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

這種規定被視爲JavaScript語言的一大問題,由於很容易不知不覺就建立了全局變量。ES6爲了改變這一點,一方面規定,var命令和function命令聲明的全局變量,依舊是全局對象的屬性;另外一方面規定,let命令、const命令、class命令聲明的全局變量,不屬於全局對象的屬性。

var a = 1;// 若是在Node的REPL環境,能夠寫成global.a// 或者採用通用方法,寫成this.awindow.a // 1let b = 1;window.b // undefined

上面代碼中,全局變量avar命令聲明,因此它是全局對象的屬性;全局變量blet命令聲明,因此它不是全局對象的屬性,返回undefined

相關文章
相關標籤/搜索