本節,我們將演示如何使用ES6的一些新功能。這既對日常的JavaScript編碼有用,也可以簡化本書後面章節中的例子。我們將介紹以下功能。
let
和const
模板字面量
解構
展開操作符
箭頭函數:
=>
類
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 PI
或let 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
函數。可以執行如下代碼來傳入參數x
、y
和z
:
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執行上面的例子。
繼承
除了新的聲明類的方式,類的繼承也有簡化的語法。我們看一個例子:
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執行上面的例子。
使用屬性存取器
使用新的類語法也可以為屬性創建存取器函數。雖然不像其他面向對像語言(封裝概念),類的屬性不是私有的,但最好還是遵循一種命名模式。
下面的例子是一個聲明了
get
和set
函數的類: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);
要聲明
get
和set
函數,只需要在我們要暴露和使用的函數名前面加上get
或set
關鍵字(行{2}
和行{3}
)。我們可以用相同的名字聲明類屬性,或者在屬性名前面加下劃線(行{1}
),讓這個屬性看起來像是私有的。然後,只要像普通的屬性一樣,引用它們的名字(行
{4}
和行{5}
),就可以執行get
和set
函數。_name
並非真正的私有屬性,我們仍然可以引用它。本書後面的章節還會談到這一點。你可以訪問https://goo.gl/SMRYsv執行上面的例子。
其他功能
ES6還有其他一些功能,包括列表迭代器、類型數組、
Set
、Map
、WeakSet
、WeakMap
、模塊、尾調用、Symbol
,等等。本書的其他章節會介紹其中部分功能。更多關於ES6全部功能和規範的信息,請參考http://www.ecma-international.org/ecma-262/6.0/。