讀古今文學網 > 學習JavaScript數據結構與算法(第2版) > 1.9 ECMAScript 6的功能 >

1.9 ECMAScript 6的功能

本節,我們將演示如何使用ES6的一些新功能。這既對日常的JavaScript編碼有用,也可以簡化本書後面章節中的例子。我們將介紹以下功能。

  • letconst

  • 模板字面量

  • 解構

  • 展開操作符

  • 箭頭函數:=>

1.9.1 用let替代var聲明變量

到ES5為止,我們可以在代碼中任意位置聲明變量,甚至重寫已聲明的變量,代碼如下:

var framework = \'Angular\';
var framework = \'React\';
console.log(framework);

  

上面代碼的輸出是React,這個值被賦給最後聲明的framework變量。這段代碼中有兩個同名的變量,這是非常危險的,可能會導致錯誤的輸出。

C、Java、C#等其他語言不允許這種行為。ES6引入了一個let關鍵字,它是新的var,這意味著我們可以直接把var關鍵字都替換成let。以下代碼就是一個例子:

let language = \'JavaScript!\'; // {1}
let language = \'Ruby!\'; // {2} - 拋出錯誤
console.log(language);

  

{2}會拋出錯誤,因為在同一作用域中已經聲明過language變量(行{1})。後面會討論let和變量作用域。

 你可以訪問https://goo.gl/he0udZ,測試和執行上面的代碼。

let的變量作用域

我們通過下面這個例子(https://goo.gl/NbsVvg),來理解let關鍵字聲明的變量如何工作:

let movie = \'Lord of the Rings\'; //{1}
//var movie = \'Batman v Superman\'; //拋出錯誤,movie變量已聲明

function starWarsFan{
  let movie = \'Star Wars\'; //{2}
  return movie;
}

function marvelFan{
  movie = \'The Avengers\'; //{3}
  return movie;
}

function blizzardFan{
  let isFan = true;
  let phrase = \'Warcraft\'; //{4}
  console.log(\'Before if: \' + phrase);
  if (isFan){
    let phrase = \'initial text\'; //{5}
    phrase = \'For the Horde!\'; //{6}
    console.log(\'Inside if: \' + phrase);
  }
  phrase = \'For the Alliance!\'; //{7}
  console.log(\'After if: \' + phrase);
}

console.log(movie); //{8}
console.log(starWarsFan); //{9}
console.log(marvelFan); //{10}
console.log(movie); //{11}
blizzardFan; //{12}

  

以上代碼的輸出如下:

Lord of the Rings
Star Wars
The Avengers
The Avengers
Before if: Warcraft
Inside if: For the Horde!
After if: For the Alliance!

  

現在,我們來討論得到這些輸出的原因。

  • 我們在行{1}聲明了一個movie變量並賦值為Lord of the Rings,然後在行{8}輸出它的值。你在1.3.1節中的「變量作用域」部分已經學過,這個變量擁有全局作用域。

  • 我們在行{9}執行了starWarsFan函數。在這個函數里,我們也聲明了一個movie變量(行{2})。這個函數的輸出是Star Wars,因為行{2}的變量擁有局部作用域,也就是說它只在函數內部可見。

  • 我們在行{10}執行了marvelFan函數。在這個函數里,我們改變了movie變量的值(行{3})。這個變量是行{1}聲明的全局變量。因此,行{11}的全局變量輸出和行{10}的輸出相同,都是The Avengers

  • 最後,我們在行{12}執行了blizzardFan函數。在這個函數里,我們聲明了一個擁有函數內作用域的phrase變量(行{4})。然後,又聲明了一個phase變量(行{5}),但這個變量的作用域只在if語句內。

  • 我們在行{6}改變了phrase的值。由於還在if語句內,值發生改變的是在行{5}聲明的變量。

  • 然後,我們在行{7}再次改變了phrase的值,但由於不是在if語句內,行{4}聲明的變量的值改變了。

作用域的行為與在Java或C等其他編程語言中一樣。然而,這是ES6才引入到JavaScript的。

1.9.2 常量

ES6還引入了const關鍵字。它的行為和let關鍵字一樣,唯一的區別在於,用const定義的變量是只讀的,也就是常量。

舉例來說,考慮如下代碼:

const PI = 3.141593;
PI = 3.0; //拋出錯誤
console.log(PI);

  

當我們試圖把一個新的值賦給 PI,甚至只是用var PIlet PI重新聲明時,代碼就會拋出錯誤,告訴我們PI是只讀的。

 你可以訪問https://goo.gl/4xuWrC執行上面的例子。

1.9.3 模板字面量

模板字面量真的很棒,因為我們創建字符串的時候不必再拼接值。

舉例來說,考慮如下ES5代碼:

var book = {
  name: \'學習JavaScript數據結構與算法\'
};
console.log(\'你正在閱讀\' + book.name + \'。n這是新的一行n 這也是。\');

  

我們可以用如下代碼改進上面這個console.log輸出的語法:

console.log(`你正在閱讀${book.name}。
這是新的一行
這也是。`);

  

模板字面量用一對`包裹。要插入變量的值,只要把變量放在${}裡就可以了,就像例子中的book.name

模板字面量也可以用於多行的字符串。再也不需要用n了。只要按下鍵盤上的Enter就可以換一行,就像上面例子裡的這是新的一行。

這個功能對簡化我們例子的輸出非常有用!

 你可以訪問https://goo.gl/PTqnwO執行上面的例子。

1.9.4 箭頭函數

ES6的箭頭函數極大地簡化了函數的語法。考慮如下例子:

var circleArea = function circleArea(r) {
  var PI = 3.14;
  var area = PI * r * r;
  return area;
};
console.log(circleArea(2));

  

上面這段代碼的語法可以簡化為如下代碼:

let circleArea = (r) => { //{1}
  const PI = 3.14;
  let area = PI * r * r;
  return area;
}
console.log(circleArea(2));

  

這個例子最大的區別在於行{1},我們可以省去function關鍵字,只用=>

如果函數只有一條語句,還可以變得更簡單,連return關鍵字都可以省去。看看下面的代碼:

let circleArea2 = (r) => 3.14 * r * r;
console.log(circleArea2(2));

  

 你可以訪問https://goo.gl/CigniJ執行上面的例子。

1.9.5 函數的參數默認值

在ES6里,函數的參數還可以定義默認值。下面是一個例子:

function sum (x = 1, y = 2, z = 3) {
  return x + y + z
};
console.log(sum(4,2)); //輸出9

  

由於我們沒有傳入參數z,它的值默認為3。因此,4 + 2 + 3 == 9

在ES6之前,上面的函數我們只能寫成這樣:

function sum (x, y, z) {
  if (x === undefined)
    x = 1;
  if (y === undefined)
    y = 2;
  if (z === undefined)
    z = 3;
  return x + y + z;
};

  

有了ES6的參數默認值,代碼可以少寫好幾行。

 你可以訪問https://goo.gl/2MiJ59執行上面的例子。

1.9.6 聲明展開和剩餘參數

在ES5中,我們可以用apply函數把數組轉化為參數。為此,ES6有了展開操作符(...)。舉例來說,考慮我們上一節聲明的sum函數。可以執行如下代碼來傳入參數xyz

var params = [3, 4, 5];
console.log(sum(...params));

  

以上代碼和下面的ES5代碼的效果是相同的:

var params = [3, 4, 5];
console.log(sum.apply(undefined, params));

  

在函數中,展開操作符(...)也可以代替arguments,當作剩餘參數使用。考慮如下例子:

function restParamaterFunction (x, y, ...a) {
  return (x + y) * a.length;
}
console.log(restParamaterFunction(1, 2, \"hello\", true, 7)); //輸出9;

  

以上代碼和下面代碼的效果是相同的:

function restParamaterFunction (x, y) {
  var a = Array.prototype.slice.call(arguments, 2);
  return (x + y) * a.length;
};

  

 你可以訪問https://goo.gl/8equk5執行展開操作符的例子,訪問https://goo.gl/LaJZqU執行剩餘參數的例子。

增強的對象屬性

ES6引入了數組解構的概念,可以用來一次初始化多個變量。考慮如下例子:

var [x, y] = [\'a\', \'b\'];

  

以上代碼和下面代碼的效果是相同的:

var x = \'a\';
var y = \'b\';

  

數組解構也可以用來進行值的互換,而不需要創建臨時變量,如下:

[x, y] = [y, x];

以上代碼和下面代碼的效果是相同的:

var temp = x;
x = y;
y = temp;

  

這對你學習排序算法會很有用,因為互換值的情況很常見。

還有一個稱為屬性簡寫的功能,它是對像解構的另一種方式。考慮如下例子:

var [x, y] = [\'a\', \'b\'];
var obj = { x, y };
console.log(obj); // { x: \"a\", y: \"b\" }

  

以上代碼和下面代碼的效果是相同的:

var x = \'a\';
var y = \'b\';
var obj2 = { x: x, y: y };
console.log(obj2); // { x: \"a\", y: \"b\" }

  

本節我們要討論的最後一個功能是方法屬性。這使得開發者可以在對像中聲明看起來像屬性的函數。下面是一個例子:

var hello = {
  name : \'abcdef\',
  printHello {
    console.log(\'Hello\');
  }
}
console.log(hello.printHello);

  

以上代碼也可以寫成下面這樣:

var hello = {
  name: \'abcdef\',
  printHello: function printHello {
    console.log(\'Hello\');
  }
};

  

你可以訪問以下URL執行上面三個例子。

  • 數組解構:https://goo.gl/VsLecp

  • 變量互換:https://goo.gl/EyFAII

  • 屬性簡寫:https://goo.gl/DKU2PN

1.9.7 使用類進行面向對像編程

ES6還引入了一種更簡潔的聲明類的方式。在1.6節,你已經學習了像下面這樣聲明一個Book類的方式:

function Book(title, pages, isbn){ //{1}
  this.title = title;
  this.pages = pages;
  this.isbn = isbn;
}
Book.prototype.printTitle = function{
  console.log(this.title);
};

  

我們可以用ES6把語法簡化如下:

class Book { //{2}
  constructor (title, pages, isbn) {
    this.title = title;
    this.pages = pages;
    this.isbn = isbn;
  }
  printIsbn{
    console.log(this.isbn);
  }
}

  

只需要使用class關鍵字,聲明一個有constructor函數和諸如printIsbn等其他函數的類。行{1}聲明Book類的代碼與行{2}聲明的代碼具有相同的效果和輸出:

let book = new Book(\'title\', \'pag\', \'isbn\');
console.log(book.title);  //輸出圖書標題
book.title = \'new title\'; //更新圖書標題
console.log(book.title);  //輸出圖書標題

  

 你可以訪問https://goo.gl/UhK1n4執行上面的例子。

  1. 繼承

    除了新的聲明類的方式,類的繼承也有簡化的語法。我們看一個例子:

    class ITBook extends Book { //{1}
      constructor (title, pages, isbn, technology) {
        super(title, pages, isbn); //{2}
        this.technology = technology;
      }
      printTechnology{
        console.log(this.technology);
      }
    }
     
    let jsBook = new ITBook(\'學習JS算法\', \'200\', \'1234567890\', \'JavaScript\');
    console.log(jsBook.title);
    console.log(jsBook.printTechnology);
    
      

    我們可以用extends關鍵字擴展一個類並繼承它的行為(行{1})。在構造函數中,我們也可以通過super關鍵字引用父類的構造函數(行{2})。

    儘管在JavaScript中聲明類的新方式的語法與Java、C、C++等其他編程語言很類似,但JavaScript面向對像編程還是基於原型實現的。

     你可以訪問https://goo.gl/hgQvo9執行上面的例子。

  2. 使用屬性存取器

    使用新的類語法也可以為屬性創建存取器函數。雖然不像其他面向對像語言(封裝概念),類的屬性不是私有的,但最好還是遵循一種命名模式。

    下面的例子是一個聲明了getset函數的類:

    class Person {
      constructor (name) {
        this._name = name; //{1}
      }
      get name { //{2}
        return this._name;
      }
      set name(value) { //{3}
        this._name = value;
      }
    }
     
    let lotrChar = new Person(\'Frodo\');
    console.log(lotrChar.name); //{4}
    lotrChar.name = \'Gandalf\'; //{5}
    console.log(lotrChar.name);
    lotrChar._name = \'Sam\'; //{6}
    console.log(lotrChar.name);
    
      

    要聲明getset函數,只需要在我們要暴露和使用的函數名前面加上getset關鍵字(行{2}和行{3})。我們可以用相同的名字聲明類屬性,或者在屬性名前面加下劃線(行{1}),讓這個屬性看起來像是私有的。

    然後,只要像普通的屬性一樣,引用它們的名字(行{4}和行{5}),就可以執行getset 函數。

    _name並非真正的私有屬性,我們仍然可以引用它。本書後面的章節還會談到這一點。

    你可以訪問https://goo.gl/SMRYsv執行上面的例子。

  3. 其他功能

    ES6還有其他一些功能,包括列表迭代器、類型數組、SetMapWeakSetWeakMap、模塊、尾調用、Symbol,等等。本書的其他章節會介紹其中部分功能。

     更多關於ES6全部功能和規範的信息,請參考http://www.ecma-international.org/ecma-262/6.0/。