ECMAScript6 (ES6、ES2015)新特性詳解


ES6 新特性

 

 

一、ES6簡介

​ 歷時將近6年的時間來制定的新 ECMAScript 標准 ECMAScript 6(亦稱 ECMAScript Harmony,簡稱 ES6)終於在 2015 年 6 月正式發布。自從上一個標准版本 ES5 在 2009 年發布以后,ES6 就一直以新語法新特性的優越性吸引著眾多 JavaScript 開發者,驅使他們積極嘗鮮。

​ 由於ES6是在2015年發布的,所以也叫ES2015。

​ 以后ESCMAScript標准一年一更新,統一使用年份命名:ES2016、ES2017、….

下面開始介紹ES6常用的一些新特性:

二、塊級作用域綁定

在ES5之前,不存在塊級作用域,在編程的時候很多時候會帶來很多的不便,ES6新增了塊級作用域,補足了這方面的缺陷。

塊級聲明指的是該聲明的變量無法被代碼塊外部訪問。塊作用域,又被稱為詞法作用域(lexical scopes),可以在如下的條件下創建:

  • 函數內部
  • 在代碼塊(即 { })內部

塊級作用域是很多類C語言的工作機制,ECMAScript 6 引入塊級聲明的目的是增強 javascript 的靈活性,同時又能與其它編程語言保持一致。

2.1 let聲明

使用let聲明變量的語法和使用var聲明的語法是一樣的。但是let聲明的變量的作用域會限制在當前的代碼塊中。這是let與var的最大區別

<script type="text/javascript"> let a = 10; if(a > 5){ console.log(b); //用let聲明的變量沒有聲明提前這一特性,所以此處也訪問不到(報錯) let b = 20; console.log(b); } console.log(b); //由於b是在if塊中使用let聲明的,所以此處無法訪問到。(報錯) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意:

  1. 用 let 聲明的變量具有塊級作用域,只能在聲明的塊中訪問,在塊外面無法訪問
  2. 用let聲明的變量也沒有聲明提前這一特性。
  3. 在同一個塊中,let聲明的變量也不能重復聲明。
  4. 在聲明變量的時候盡量使用let,慢慢的拋棄var

2.2 const聲明(Constant Declarations)

在 ES6 使用const來聲明的變量稱之為常量。這意味着它們不能再次被賦值。由於這個原因,所有的 const 聲明的變量都必須在聲明處初始化。const聲明的常量和let變量一樣也是具有塊級作用域的特性。

<script type="text/javascript"> var a = 20; if (true) { const b = 20; b = 30; //錯誤! 常量不能重新賦值 const c; //錯誤! 常量聲明的同時必須賦值。 } </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意:

  1. const的特性除了聲明的是常量為,其他與let一樣。
  2. 在let和const聲明前的這段區域稱之為暫存性死區(The Temporal Dead Zone —TDZ)。
  3. 使用let和const聲明的變量和常量不再是window的屬性。 也就是說通過window.a是無法訪問到的。

2.3 循環中的塊級綁定

使用var聲明的循環變量在循環結束后仍然可以訪問到。 使用let聲明的循環變量,在循環結束之后會立即銷毀。

<script type="text/javascript"> for(let i = 0; i < 3; i++){ // 循環結束之后會立即銷毀 i console.log(i); } console.log(i); //此處無法訪問到 i 。 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.4 循環中的函數

看下面的代碼,是輸出10個10,而不是0,1,2,…

<script type="text/javascript"> var funcs = []; for (var i = 0; i < 10; i++) { funcs.push(function () { console.log(i); }); } funcs.forEach(function (func) { func(); // 輸出 "10" 共10次 }); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

解決辦法需要使用函數的自執行特性。

var funcs = []; for (var i = 0; i < 10; i++) { funcs.push((function(value) { return function() { console.log(value); } }(i))); } funcs.forEach(function(func) { func(); // 輸出 0,1,2 ... 9 });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如果使用let聲明變量,則完全可以避免前面的問題。 這是ES6規范中專門定義的特性。在for … in和for … of循環中也適用

<script type="text/javascript"> var funcs = []; for (let i = 0; i < 10; i++) { funcs.push(function () { console.log(i); }); } funcs.forEach(function (func) { func(); // 輸出 0,1,2 ... 9 }) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

說明:

  1. let 聲明使得每次迭代都會創建一個變量 i,所以循環內部創建的函數會獲得各自的變量 i 的拷貝。每份拷貝都會在每次迭代的開始被創建並被賦值。

三、函數的新增特性

3.1 帶默認參數的函數

JavaScript函數的最大的一個特點就是在傳遞參數的時候,參數的個數不受限制的。為了健壯性考慮,一般在函數內部需要做一些默認值的處理。

function makeRequest(url, timeout, callback) { timeout = timeout || 2000; callback = callback || function() {}; }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

其實上面的默認值方法有個bug:當timeout是0的時候也會當做假值來處理,從而給賦值默認值2000.

ES6從語言層面面上增加了 默認值的 支持。看下面的代碼:

//這個函數如果只傳入第一個參數,后面兩個不傳入,則會使用默認值。如果后面兩個也傳入了參數,則不會使用默認值。 function makeRequest(url, timeout = 2000, callback = function() {}) { // 其余代碼 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.2 默認參數對 arguments 對象的影響

在非嚴格模式下,arguments總是能反映出命名參數的變化。看下面的代碼:

<script type="text/javascript"> function foo(a, b) { //非嚴格模式 console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true a = 10; b = 20; console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true } foo(1, 2); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在ES5的嚴格模式下,arguments只反映參數的初始值,而不再反映命名參數的變化!

<script type="text/javascript"> function foo(a, b) { //嚴格模式  "use strict" console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true a = 10; b = 20; console.log(arguments[0] === a); //false。 修改a的值不會影響到arguments[0]的值 console.log(arguments[1] === b); //false } foo(1, 2); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

當使用ES6參數默認值的時候,不管是否是在嚴格模式下,都和ES5的嚴格模式相同。看下面的代碼:

<script type="text/javascript"> function foo(a, b = 30) { console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true a = 10; b = 20; console.log(arguments[0] === a); //false。 由於b使用了默認值。雖然a沒有使用默認值,但是仍然表現的和嚴格模式一樣。 console.log(arguments[1] === b); //false。 b使用了默認值,所以表現的和嚴格模式一樣。 } foo(1, 2); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意:如果這樣調用foo(1),則 a == 1, b == 30, arguments[0] == 1, arguments[1] == undefined。也就是說默認值並不會賦值給arguments參數。

3.3 默認參數表達式 (Default Parameter Expressions)

參數的默認值,也可以是一個表達式或者函數調用等。看下面的代碼

<script type="text/javascript"> function getValue() { return 5; } function add(first, second = getValue()) { //表示使用getValue這個函數的返回值作為second的默認值。 return first + second; } console.log(add(1, 1)); // 2. 調用add函數的時候,傳入了第二個參數,則以傳入的參數為准。 console.log(add(1)); // 6。 調用add函數的時候,沒有傳入第二個參數,則會調用getValue函數。 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

有一點需要要注意:getValue()只會在調用add且不傳入第二個參數的時候才會去調用。不是在解析階段調用的。

<script type="text/javascript"> let value = 5; function getValue() { return value++; } function add(first, second = getValue()) { // return first + second; } console.log(add(1, 1)); // 2 console.log(add(1)); // 6。 console.log(add(1)); // 7 console.log(add(1)); // 8 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

由於默認值可以表達式,所以我們甚至可以使用前面的參數作為后面參數的默認值。

function add(first, second = first) { // 使用第一個參數作為第二個參數的默認值 return first + second; } 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

注意:可以把前面的參數作為后面參數的默認值,但是不能把后面的參數作為第一個參數的默認值。這可以前面說的let和const的暫存性死區一個意思。

function add(first = second, second)) { // 這種寫法是錯誤的 return first + second; }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

3.4 未命名參數問題

Javascript並不限制傳入的參數的數量。在調用函數的時候,傳入的實參的個數超過形參的個數的時候,超過的部分就成為了未命名參數。在ES5之前,我們一般可以通過arguments對象來獲取到未命名參數的值。但是羅顯繁瑣。

<script type="text/javascript"> function foo(a) { console.log(a); console.log(arguments[1]) //取得傳入的多余的參數。 } foo(2, 3); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

ES6,提供了一種更加優雅處理未命名參數的問題:剩余參數Rest Parameters )

語法:function a(a, … b){ }

剩余參數使用三個點( … )和變量名來表示。

<script type="text/javascript"> function foo(a, ...b) { console.log(a); console.log(b instanceof Array); //true .多余的參數都被放入了b中。b其實就是一個數組。 } foo(2, 3, 4, 6); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意:

  1. 函數最多只能有一個剩余參數b。而且這個剩余參數必須位於參數列表的最后位置。
  2. 雖然有了剩余參數,但是arguments仍然存在,但是arguments完全無視了剩余參數的存在。
  3. 剩余參數是在函數聲明的時候出現的。

3.5 函數中的擴展運算符

例如:Math中的max函數可以返回任意多個參數中的最大值。但是如果這些參數在一個數組中,則沒有辦法直接傳入。以前通用的做法是使用applay方法。

看下面的代碼:

<script type="text/javascript"> let values = [25, 50, 75, 100] console.log(Math.max.apply(Math, values)); // 100 </script>
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

上面這種方法雖然可行,但是總是不是那么直觀。

使用ES6提供的擴展運算符可以很容易的解決這個問題。在數組前加前綴 … (三個點)。

<script type="text/javascript"> let values = [25, 50, 75, 100] console.log(Math.max(...values)); //使用擴展運算符。相當於拆解了數組了。 console.log(Math.max(...values, 200)); //也可以使用擴展運算符和參數的混用,則這個時候就有 5 個數參與比較了。 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

注意:剩余參數和擴展運算符都是 使用三個點作為前綴。但是他們使用的位置是不一樣的。

  1. **剩余參數是用在函數的聲明的時候的參數列表中,而且必須在參數列表的后面
  2. 擴展運算符是用在函數調用的時候作為實參來傳遞的,在實參中的位置沒有限制。

四、全新的函數:箭頭函數(=>)

ECMAScript 6 最有意思的部分之一就是箭頭函數。正如其名,箭頭函數由 “箭頭”(=>)這種新的語法來定義。

其實在別的語言中早就有了這種語法結構,不過他們叫拉姆達表達式。

4.1 箭頭函數語法

基本語法如下:

(形參列表)=>{
  //函數體 }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

箭頭函數可以賦值給變量,也可以像匿名函數一樣直接作為參數傳遞。

  • 示例1:
<script type="text/javascript"> var sum = (num1, num2) =>{ return num1 + num2; } console.log(sum(3, 4)); //前面的箭頭函數等同於下面的傳統函數 var add = function (num1, num2) { return num1 + num2; } console.log(add(2, 4)) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如果函數體內只有一行代碼,則包裹函數體的 大括號 ({ })完全可以省略。如果有return,return關鍵字也可以省略。

如果函數體內有多條語句,則 {} 不能省略。

  • 示例2:
<script type="text/javascript"> var sum = (num1, num2) => num1 + num2; console.log(sum(5, 4)); //前面的箭頭函數等同於下面的傳統函數 var add = function (num1, num2) { return num1 + num2; } console.log(add(2, 4)); //如果這一行代碼是沒有返回值的,則方法的返回自也是undefined var foo = (num1, num2) => console.log("aaa"); console.log(foo(3,4)); //這個地方的返回值就是undefined </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

如果箭頭函數只有一個參數,則包裹參數的小括號可以省略。其余情況下都不可以省略。當然如果不傳入參數也不可以省略

  • 示例3:
<script type="text/javascript"> var foo = a=> a+3; //因為只有一個參數,所以()可以省略 console.log(foo(4)); // 7 </script>
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

如果想直接返回一個js對象,而且還不想添加傳統的大括號和return,則必須給整個對象添加一個小括號 ()

  • 示例4:
<script type="text/javascript"> var foo = ()=>({name:"lisi", age:30}); console.log(foo()); //等同於下面的; var foo1 = ()=>{ return { name:"lisi", age : 30 }; } </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.2 使用箭頭函數實現函數自執行

<script type="text/javascript"> var person = (name => {  return { name: name, age: 30 } } )("zs"); console.log(person); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.3 箭頭函數中無this綁定(No this Binding)

在ES5之前this的綁定是個比較麻煩的問題,稍不注意就達不到自己想要的效果。因為this的綁定和定義位置無關,只和調用方式有關。

在箭頭函數中則沒有這樣的問題,在箭頭函數中,this和定義時的作用域相關,不用考慮調用方式

箭頭函數沒有 this 綁定,意味着 this 只能通過查找作用域鏈來確定。如果箭頭函數被另一個不包含箭頭函數的函數囊括,那么 this 的值和該函數中的 this 相等,否則 this 的值為 window。

<script type="text/javascript"> var PageHandler = { id: "123456", init: function () { document.addEventListener("click", event => this.doSomething(event.type), false); // 在此處this的和init函數內的this相同。 }, doSomething: function (type) { console.log("Handling " + type + " for " + this.id); } }; PageHandler.init(); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

看下面的一段代碼:

<script type="text/javascript"> var p = { foo:()=>console.log(this) //此處this為window } p.foo(); //輸出為 window對象。 並不是我想要的。所以在定義對象的方法的時候應該避免使用箭頭函數。 //箭頭函數一般用在傳遞參數,或者在函數內部聲明函數的時候使用。 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

說明:

  1. 箭頭函數作為一個使用完就扔的函數,不能作為構造函數使用。也就是不能使用new 的方式來使用箭頭函數。
  2. 由於箭頭函數中的this與函數的作用域相關,所以不能使用call、apply、bind來重新綁定this。但是雖然this不能重新綁定,但是還是可以使用call和apply方法去執行箭頭函數的。

4.4 無arguments綁定

雖然箭頭函數沒有自己的arguments對象,但是在箭頭函數內部還是可以使用它外部函數的arguments對象的。

<script type="text/javascript"> function foo() { //這里的arguments是foo函數的arguments對象。箭頭函數自己是沒有 arguments 對象的。 return ()=>arguments[0]; //箭頭函數的返回值是foo函數的第一個參數 } var arrow = foo(4, 5); console.log(arrow()); // 4 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

五、對象功能的擴展

在JavaScript中,幾乎所有的類型都是對象,所以使用好對象,對提示JavaScript的性能很重要。

ECMAScript 6 給對象的各個方面,從簡單的語法擴展到操作與交互,都做了改進。

5.1 對象類別

ECMAScript 6 規范明確定義了每種對象類別。理解該術語對於從整體上認識該門語言顯得十分重要。對象類別包括:

  • 普通對象(ordinary object)擁有 JavaScript 對象所有的默認行為。
  • 特異對象(exotic object)的某些內部行為和默認的有所差異。
  • 標准對象(standard object)是 ECMAScript 6 中定義的對象,例如 Array, Date 等,它們既可能是普通也可能是特異對象。
  • 內置對象(built-in object)指 JavaScript 執行環境開始運行時已存在的對象。標准對象均為內置對象。

5.2 對象字面量的語法擴展

5.2.1 簡寫的屬性初始化

<script type="text/javascript"> function createPerson(name, age) { //返回一個對象:屬性名和參數名相同。 return { name:name, age:age } } console.log(createPerson("lisi", 30)); // {name:"lisi", age:30} //在ES6中,上面的寫法可以簡化成如下形式 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在ES6中,上面的寫法可以簡化成如下的形式:

<script type="text/javascript"> function createPerson(name, age) { //返回一個對象:屬性名和參數名相同。 return { name, //當對象屬性名和本地變量名相同時,可以省略冒號和值 age } } console.log(createPerson("lisi", 30)); // {name:"lisi", age:30} </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

當對象字面量中的屬性只有屬性名的時候,JavaScript 引擎會在該作用域內尋找是否有和屬性同名的變量。在本例中,本地變量 name 的值被賦給了對象字面量中的 name 屬性。

該項擴展使得對象字面量的初始化變得簡明的同時也消除了命名錯誤。對象屬性被同名變量賦值在 JavaScript 中是一種普遍的編程模式,所以這項擴展的添加非常受歡迎。

5.2.2 簡寫的方法聲明

<script type="text/javascript"> var person = { name:'lisi', sayHell:function () { console.log("我的名字是:" + this.name); } } person.sayHell() </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在ES6中,上面的寫法可以簡化成如下的形式:

<script type="text/javascript"> var person = { name:'李四', sayHell() { console.log("我的名字是:" + this.name); } } person.sayHell() </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

省略了冒號和function看起來更簡潔

5.2.3 在字面量中動態計算屬性名

在ES5之前,如果屬性名是個變量或者需要動態計算,則只能通過 對象.[變量名] 的方式去訪問。而且這種動態計算屬性名的方式 在字面量中 是無法使用的。

<script type="text/javascript"> var p = { name : '李四', age : 20 } var attName = 'name'; console.log(p[attName]) //這里 attName表示的是一個變量名。 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

而下面的方式使用時沒有辦法訪問到attName這個變量的。

<script type="text/javascript"> var attName = 'name'; var p = { attName : '李四', // 這里的attName是屬性名,相當於各級p定義了屬性名叫 attName的屬性。 age : 20 } console.log(p[attName]) // undefined </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在ES6中,把屬性名用[ ]括起來,則括號中就可以引用提前定義的變量。

<script type="text/javascript"> var attName = 'name'; var p = { [attName] : '李四', // 引用了變量attName。相當於添加了一個屬性名為name的屬性 age : 20 } console.log(p[attName]) // 李四 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5.3 新增的方法

ECMAScript 從第五版開始避免在 Object.prototype 上添加新的全局函數或方法,轉而去考慮具體的對象類型如數組)應該有什么方法。當某些方法不適合這些具體類型時就將它們添加到全局 Object 上 。

ECMAScript 6 在全局 Object 上添加了幾個新的方法來輕松地完成一些特定任務。

5.3.1 Object.is()

在 JavaSciprt 中當你想比較兩個值時,你極有可能使用比較操作符(==)或嚴格比較操作符(===)。許多開發者為了避免在比較的過程中發生強制類型轉換,更傾向於后者。但即使是嚴格等於操作符,它也不是萬能的。例如,它認為 +0 和 -0 是相等的,雖然它們在 JavaScript 引擎中表示的方式不同。同樣 NaN === NaN 會返回 false,所以必須使用 isNaN() 函數才能判斷 NaN 。

ECMAScript 6 引入了 Object.is() 方法來補償嚴格等於操作符怪異行為的過失。該函數接受兩個參數並在它們相等的返回 true 。只有兩者在類型和值都相同的情況下才會判為相等。如下所示:

console.log(+0 == -0); // true console.log(+0 === -0); // true console.log(Object.is(+0, -0)); // false console.log(NaN == NaN); // false console.log(NaN === NaN); // false console.log(Object.is(NaN, NaN)); // true console.log(5 == 5); // true console.log(5 == "5"); // true console.log(5 === 5); // true console.log(5 === "5"); // false console.log(Object.is(5, 5)); // true console.log(Object.is(5, "5")); // false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

很多情況下 Object.is() 的表現和 === 是相同的。它們之間的區別是前者 認為 +0 和 -0 不相等而 NaN 和 NaN 則是相同的。不過棄用后者是完全沒有必要的。何時選擇 Object.is() 與 == 或 === 取決於代碼的實際情況。

5.3.2 Object.assign()

使用assign主要是為了簡化對象的混入(mixin)。混入是指的在一個對象中引用另一個對象的屬性或方法。

assing可以把一個對象的屬性和訪問完整的轉copy到另外一個對象中。

<script type="text/javascript"> var p = { name : "lisi", age : 20, friends : ['張三', '李四'] } var p1 = {}; Object.assign(p1, p); //則p1中就有了與p相同的屬性和方法. p1是接受者,p是提供者 console.log(p1); //這種copy是淺copy,也就是說如果屬性值是對象的話,只是copy的對象的地址值(引用) console.log(p1.friends == p.friends); //true p1和p的friends同事指向了同一個數組。 p.friends.push("王五"); console.log(p1.friends); //['張三', '李四', '王五'] </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

assign方法可以接受任意多的提供者。意味着后面提供者的同名屬性和覆蓋前面提供者的屬性值。

<script type="text/javascript"> var p = { name : "lisi", age : 20, friends : ['張三', '李四'] } var p1 = { name : 'zs', } var p2 = {}; Object.assign(p2, p, p1); //p和p1都是提供者 console.log(p2.name); // zs </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

六、字符串功能的增強

6.1 查找子字符串

在以前在字符串中查找字符串的時候,都是使用indexOf方法。

ES6新增了三個方法來查找字符串。

  • includes() 方法會在給定文本存在於字符串中的任意位置時返回 true,否則返回 false 。
  • startsWith() 方法會在給定文本出現在字符串開頭時返回 true,否則返回 false 。
  • endsWith() 方法會在給定文本出現在字符串末尾時返回 true,否則返回 false 。

每個方法都接收兩個參數:需要搜索的文本和可選的起始索引值。當提供第二個參數后,includes() 和 startsWith() 會以該索引為起始點進行匹配,而 endsWith() 將字符串的長度與參數值相減並將得到的值作為檢索的起始點。若第二個參數未提供,includes() 和 startsWith() 會從字符串的起始中開始檢索,endsWith() 則是從字符串的末尾。實際上,第二個參數減少了需要檢索的字符串的總量。以下是使用這些方法的演示:

var msg = "Hello world!"; console.log(msg.startsWith("Hello")); // true console.log(msg.endsWith("!")); // true console.log(msg.includes("o")); // true console.log(msg.startsWith("o")); // false console.log(msg.endsWith("world!")); // true console.log(msg.includes("x")); // false console.log(msg.startsWith("o", 4)); // true console.log(msg.endsWith("o", 8)); // true console.log(msg.includes("o", 8)); // false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

6.2 repeat方法

ECMAScript 6 還向字符串添加了 repeat() 方法,它接受一個數字參數作為字符串的重復次數。該方法返回一個重復包含初始字符串的新字符串,重復次數等於參數。例如:

console.log("x".repeat(3)); // "xxx" console.log("hello".repeat(2)); // "hellohello" console.log("abc".repeat(4)); // "abcabcabcabc"
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

6.3 字符串模板字面量

模板字面量是 ECMAScript 6 針對 JavaScript 直到 ECMAScript 5 依然缺失的如下功能的回應:

  • 多行字符串 針對多行字符串的形式概念(formal concept)。
  • 基本的字符串格式化 將字符串中的變量置換為值的能力。
  • 轉義 HTML 能將字符串進行轉義並使其安全地插入到 HTML 的能力。

模板字面量以一種全新的表現形式解決了這些問題而不需要向 JavaScript 已有的字符串添加額外的功能。

6.3.1 基本語法

使用一對反引號 “(tab正上方的按鍵)來表示模板字面量。

let message = `Hello world!`; //使用模板字面量創建了一個字符串 console.log(message); // "Hello world!" console.log(typeof message); // "string" console.log(message.length); // 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

注意:如果模板字符串中使用到了反引號,則應該轉義。但是單雙引號不需要轉義

6.3.2 多行字符串

在ES5之前JavaScript是不支持多行字符串的。(但是在以前的版本中有一個大家都認為是bug的方式可以寫出多行字符串,就是在尾部添加一個反斜杠 \)

<body> <script type="text/javascript"> var s = "abc \ aaaaaa"; console.log(s); //但是輸出的結果中不包括換行 </script> </body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

但是在ES6中字符串的模板字面量輕松的解決了多行字符串的問題,而且沒有任何新的語法

<script type="text/javascript"> var s = `abc aaaaa dsalfja dfadfja`; console.log(s); </script> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

但是要注意: 反引號中的所有空格和縮進都是有效字符。

6.3.3 字符串置換

置換允許你將 JavaScript 表達式嵌入到模板字面量中並將其結果作為輸出字符串中的一部分。

語法:${變量名、表達式、任意運算、方法調用等}

可以嵌入任何有效的JavaScript代碼

<script type="text/javascript"> var name = "李四"; var msg = `歡迎你${name}同學`; console.log(msg) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

6.3.4 模板標簽

6.3.4.1 什么是模板標簽

模板字面量真正的強大之處來源於模板標簽。一個模板標簽可以被轉換為模板字面量並作為最終值返回。標簽在模板的頭部,即左 ` 字符之前指定,如下所示:

let message = myTag`Hello world`;
  • 1
  • 1

在上面的代碼中,myTag就是模板標簽。

myTag其實是一個函數,這個函數會被調用來處理這個模板字符串。

6.3.4.2 定義模板標簽

一個標簽僅代表一個函數,他接受需要處理的模板字面量。標簽分別接收模板字面量中的片段,且必須將它們組合以得出結果。函數的首個參數為包含普通 JavaScript 字符串的數組。余下的參數為每次置換的對應值。

標簽函數一般使用剩余參數來定義,以便輕松地處理數據。如下:

<script type="text/javascript"> let name = '張三', age = 20, message = show`我來給大家介紹${name}的年齡是${age}.`; /* 應該定義一個函數show: 參數1:一個字符串數組。在本例中包含三個元素。 0:"我來給大家介紹" 1:"的年齡是" 2:"." 參數2和參數3:表示需要置換的字符串的值。 */ function show(stringArr, value1, value2) { console.log(stringArr); // console.log(value1); // 張三 console.log(value2); // 20 return "abc"; } console.log(message); //abc </script> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

為了簡化書寫,一般把Value1和Value2寫成剩余字符串的形式

function show(stringArr, ...values){ }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

七、解構

7.1 解構的實用性

在 ECMAScript 5 或更早的版本中,從對象或數組中獲取特定的數據並賦值給本地變量需要書寫很多並且相似的代碼。例如:

let options = { repeat: true, save: false }; // 從對象中提取數據 let repeat = options.repeat, save = options.save;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這段代碼反復地提取在 options 上存儲地屬性值並將它們傳遞給同名的本地變量。雖然這些看起來不是那么復雜,不過想象一下如果你的一大批變量有着相同的需求,你就只能一個一個地賦值。而且,如果你需要從對象內部嵌套的結構來查找想要的數據,你極有可能為了一小塊數據而訪問了整個數據結構

這也是 ECMAScript 6 給對象和數組添加解構的原因。當你想要把數據結構分解為更小的部分時,從這些部分中提取數據會更容易些。很多語言都能使用精簡的語法來實現解構操作。ECMAScript 6 解構的實際語法或許你已經非常熟悉:對象和數組字面量。

7.2 對象解構

7.2.1 對象解構的基本形式

對象結構的語法就是在賦值語句的左側使用類似對象字面量的結構。

let node = { type: "Identifier", name: "foo" }; //這里就相當於聲明了兩個變量: type = node.type; name:node.name let { type, name } = node; console.log(type); // "Identifier" console.log(name); // "foo"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在上面的結構中必須要初始化。否則會出現語法錯誤。

// 語法錯誤! var { type, name }; // 語法錯誤! let { type, name }; // 語法錯誤! const { type, name };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

7.2.2 解構賦值表達式

如果聲明的變量想改變他們的值,也可以使用解構表達式。

<script type="text/javascript"> let node = { type: "Identifier", name: "foo" }, type = "Literal", name = 5; //注意:此處必須要在圓括號內才能使用解構表達式 ({type, name} = node); console.log(type); // "Identifier" console.log(name); // "foo"" </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

7.2.3 對象解構時的默認值

如果賦值號右邊的對象中沒有與左邊變量同名的屬性,則左邊的變量會是 undefined

let node = { type: "Identifier", name: "foo" }; //因為node中沒有叫value的屬性,所以valued的值將會是undefined let { type, name, value } = node; console.log(type); // "Identifier" console.log(name); // "foo" console.log(value); // undefined
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

不過我們也可以手動指定他的默認值。(這個和函數的參數默認值很像)

<script type="text/javascript"> let node = { type: "Identifier", name: "foo" }; //手動添加value的默認值為3 let { type, name, value = 3} = node; console.log(type); // "Identifier" console.log(name); // "foo" console.log(value); // 3 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

7.2.4 賦值給不同的變量名

在前面的操作中,都是把對象的屬性值,賦值給同名變量。

其實也可以賦值給不同名的變量。

<script type="text/javascript"> let node = { type: "Identifier", name: "foo" }; // localType才是要定義的新的變量。 type是node的屬性 let {type: localType, name: localName} = node; console.log(localType); // "Identifier" console.log(localName); // "foo" </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

注意:冒號后面才是要定義的新的變量,這個可以我們的對象字面量不太一樣!

這個地方也可以使用默認值。

let node = { type: "Identifier" }; let { type: localType, name: localName = "bar" } = node; console.log(localType); // "Identifier" console.log(localName); // "bar"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

7.3 數組解構

7.3.1 數組解構基本語法

數據解構的語法和對象解構看起來類似,只是將對象字面量替換成了數組字面量,而且解構操作的是數組內部的位置(索引)而不是對象中的命名屬性,例如:

let colors = [ "red", "green", "blue" ]; let [ firstColor, secondColor ] = colors; console.log(firstColor); // "red" console.log(secondColor); // "green"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

如果只想取數組中的某一項,則可以不用命名。

let colors = [ "red", "green", "blue" ]; //只取數組中的第三項。 let [ , , thirdColor ] = colors; console.log(thirdColor); // "blue"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

7.3.2 解構表達式

你可以想要賦值的情況下使用數組的解構賦值表達式,但是和對象解構不同,沒必要將它們包含在圓括號中,例如:

let colors = [ "red", "green", "blue" ], firstColor = "black", secondColor = "purple"; [ firstColor, secondColor ] = colors; //可以不用加括號。當然添加也不犯法 console.log(firstColor); // "red" console.log(secondColor); // "green"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

數組解構表達式有一個很常用的地方,就是交換兩個變量的值。在以前一般定義一個第三方變量進行交換,例如下面的代碼:

<script type="text/javascript"> let a = 3, b = 4, temp; temp = a; a = b; b = temp; console.log(a); console.log(b) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

那么在ES6中完全可以拋棄第三方變量這種方式,使用我們的數組解構表達式

<script type="text/javascript"> let a = 3, b = 4; //左側和前面的案例是一樣的,右側是一個新創建的數組字面量。 [a, b] = [b, a]; console.log(a); console.log(b) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

八、新的基本類型:Symbol

以前我們有5種基本數據類型:Number、String、Boolean、Null、Undefined

ES6新增了一種新的數據類型:Symbol

在ES5之前我們都沒辦法創建私有變量,只能想辦法去封裝。symbol 來創建私有成員,這也是 JavaScript 開發者長久以來期待的一項特性。

8.1 創建Symbol

Symbol在基本數據類型中是比較特別的。我們以前的都可以用字面量去創建基本數據類型的數據,但是Symbol卻不可以使用字面量的是形式去創建。

我們可以使用symbol全局函數來創建Symbol。

<script type="text/javascript"> let firstName = Symbol(); //創建一個Symbol let person = {}; person[firstName] = "張三"; console.log(person[firstName]); // "張三" </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

說明:上面的代碼中,firstName 作為 symbol 類型被創建並賦值給 person 對象以作其屬性。每次訪問這個屬性時必須使用該 symbol 。

在創建Symbol的時候,也可以傳入字符串,這個字符串也僅僅是在調試輸出的時候方便,實際沒有啥用處。

<script type="text/javascript"> var s1 = Symbol("abc"); var s2 = Symbol("abc"); console.log(s1 == s2); //false </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

注意:任意兩個Symbol都不會相等,即使創建他們的時候使用了相同的參數。

8.2 識別Symbol

既然 symbol 是基礎類型,你可以使用 typeof 操作符來判斷變量是否為 symbol 。ECMAScript 6 拓展了 typeof 使其操作 symbol 時返回 “symbol”。例如:

let symbol = Symbol(); console.log(typeof symbol); // "symbol"
  • 1
  • 2
  • 1
  • 2

8.3 Symbol作為屬性名

​ 由於每一個Symbol值都是不相等的,這意味着Symbol值可以作為標識符,用於對象的屬性名,就能保證不會出現同名的屬性。這對於一個對象由多個模塊構成的情況非常有用,能防止某一個鍵被不小心改寫或覆蓋。

var mySymbol = Symbol(); // 第一種寫法 var a = {}; a[mySymbol] = 'Hello!'; // 第二種寫法 var a = { [mySymbol]: 'Hello!' }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

以上兩種寫法都是相同的結果

注意:

  1. symbol作為對象的屬性的時候,只能使用 [ ] 去訪問,不能使用點去訪問。

  2. symbol作為對象的屬性名使用的時候,該屬性還是公開屬性,不是私有屬性。但是這個時候使用for… in和for…of

    時無法遍歷到這個symbol屬性的。

8.4 Symbol屬性名的遍歷

​ Symbol 作為屬性名,該屬性不會出現在for…in、for…of循環中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,可以獲取指定對象的所有 Symbol 屬性名。

看下面的代碼

<script type="text/javascript"> var obj = {}; var a = Symbol('a'); var b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; // 返回obj對象所有Symbol類型的屬性名組成的數組。 var objectSymbols = Object.getOwnPropertySymbols(obj); console.log(objectSymbols) //[Symbol(a), Symbol(b)] </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

看下面的代碼

var obj = {}; var foo = Symbol("foo"); obj[foo] = "lisi"; for (var i in obj) { console.log(i); // 無輸出 。 因為遍歷不到Symbol型的屬性 } Object.getOwnPropertyNames(obj);// [] 只能拿到非Symbol類型的屬性 Object.getOwnPropertySymbols(obj) //[Symbol(foo)] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

還有一個新API可以拿到所有類型的屬性,包括常規和Symbol型的。

Reflect.ownKeys

let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj);// ["enum", "nonEnum", Symbol(my_key)] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

說明:

  1. 由於以 Symbol 值作為名稱的屬性,不會被常規方法遍歷得到。我們可以利用這個特性,為對象定義一些非私有的、但又希望只用於內部的方法。

8.5 Symbol.for(字符串)和Symbol.keyFor(symbol類型的值)

一、Symbol.for(字符串參數):在全局環境中搜索 以該字符串作為參數的Symbol值,如果搜到則返回這個sybol,如果搜不到則創建一個Symbol,並把它注冊在全局環境中。

    <script type="text/javascript"> //第一次搜不到,則新創建一個返回,並在全局環境(window)中注冊 var a = Symbol.for("foo"); //第二次搜到上次創建的 var b = Symbol.for("foo"); console.log(a === b); //因為兩次搜到的是同一個Symbol,所以此處是true </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Symbol.for()和Symbol()都可以創建Symbol類型的數據。

二者區別:

  1. Symbol.for()對同樣的字符串,每次得到結果肯定是一樣的。因為都是從全局環境中搜索。
  2. Symbol()則不會有搜索的過程,每次都是一個全新的不同的symbol,而且也不會向全局環境中注冊。

看下面的代碼

<script type="text/javascript"> var a = Symbol("foo"); var b = Symbol.for("foo"); console.log(a == b); //false </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

二、Symbol.keyFor(symbol):返回一個已經注冊的symbol的”key”。

<script type="text/javascript"> var a = Symbol("foo"); var b = Symbol.for("foo"); console.log(Symbol.keyFor(a)); // undefined. 因為a沒有想全局環境中登記,所以是undefinded console.log(Symbol.keyFor(b)); // foo </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

九、Set數據結構

​ JavaScript 在絕大部分歷史時期內只有一種集合類型,那就是數組。數組在 JavaScript 中的使用方式和其它語言很相似,但是其它集合類型的缺乏導致數組也經常被當作隊列(queues)和棧(stacks)來使用。

​ 因為數組的索引只能是數字類型,當開發者覺得非數字類型的索引是必要的時候會使用非數組對象。這項用法促進了以非類數組對象為基礎的 set 和 map 集合類型的實現。

Set是類似數組的一種結構,可以存儲數據,與數組的區別主要是 Set中的元素不能重復,而數組中的元素可以重復

一句話總結:Set類型是一個包含無重復元素的有序列表

9.1 創建Set和並添加元素

Set本身是一個構造函數。

<script type="text/javascript"> //創建Set數據結構對象。 var s = new Set(); //調用set對象的add方法,向set中添加元素 s.add("a"); s.add("c"); s.add("b"); //set的size屬性可以獲取set中元素的個數 console.log(s.size) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

9.2 Set中不能添加重復元素

<script type="text/javascript"> var s = new Set(); s.add("a"); s.add("c"); s.add("b"); s.add("a"); //重復,所以添加失敗。注意這個地方並不會保存。 console.log(s.size); // 長度是3 </script> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

看下面的代碼:

<script type="text/javascript"> var s = new Set(); s.add(5); s.add("5"); console.log(s.size); // 長度是2 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

​ 在上面的代碼中,數字5和字符串5都會添加成功。為什么呢?

Set是使用什么機制來判斷兩個元素是否相等的呢?

是通過我們前面說過的 Object.is(a, b) 來判斷兩個元素是否相等。

回憶一下:這個方法除了 +0和-0、NaN和NaN認為相等,其余和三個 === 是完全一樣的。

<script type="text/javascript"> var s = new Set(); s.add(+0); s.add(-0); //重復添加不進去 s.add(NaN); s.add(NaN); //重復添加不進去 s.add([]); s.add([]); //兩個空數組不相等,所以可以添加進去 s.add({}); s.add({}); // 兩個空對象也不重復,所以也可以添加進去 console.log(s.size); // 長度是6 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

9.3 使用數組初始化Set

<script type="text/javascript"> //使用數組中的元素來初始化Set,當然碰到重復的也不會添加進去。 var s = new Set([2, 3, 2, 2, 4]); console.log(s.size) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

9.4 判斷一個值是否在Set中

使用Set的 has() 方法可以判斷一個值是否在這個set中。

<script type="text/javascript"> let set = new Set(); set.add(5); set.add("5"); console.log(set.has(5)); // true console.log(set.has(6)); // false </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

9.5 移除Set中的元素

delete(要刪除的值) :刪除單個值

clear():清空所有的值

<script type="text/javascript"> let set = new Set(); set.add(5); set.add("5"); console.log(set.has(5)); // true set.delete(5); console.log(set.has(5)); // false console.log(set.size); // 1 set.clear(); console.log(set.has("5")); // false console.log(set.size); // 0 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

9.6 遍歷Set

數組有個方法forEach可以遍歷數組。

  1. Set也有forEach可以遍歷Set。

使用Set的forEach遍歷時的回調函數有三個參數:

function (value, key, ownerSet){

}

參數1:遍歷到的元素的值

參數2:對set集合來說,參數2的值和參數1的值是完全一樣的。

參數3:這個set自己

<script type="text/javascript"> let set = new Set(["a", "c", "b", 9]); set.forEach(function (v, k, s) { console.log(v + " " + (v === k) + " " + (s === set)); // 永遠是true }) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. for…of也可以遍歷set。
for(var v of set){ console.log(v) }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

9.7 將Set轉換為數組

將數組轉換為Set相當容易,你只需要在創建Set集合時把數組作為參數傳遞進去即可。

把Set轉換為數組使用前面講到的擴展運算符也很容易

<script type="text/javascript"> let set = new Set([1, 2, 3, 3, 3, 4, 5]), arr = [...set]; //使用擴展運算符。那么新的數組中已經沒有了重復元素。注意,此對set並沒有什么影響 console.log(arr); // [1,2,3,4,5] </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這種情況在需要去數組中重復元素的時候非常好用。

<script type="text/javascript"> function eliminateDuplicates(items) { return [...new Set(items)]; } let numbers = [1, 2, 3, 3, 3, 4, 5, 5, 2, 1, 1], //返回的是新的沒有重復元素的數組。 noDuplicates = eliminateDuplicates(numbers); console.log(noDuplicates); // [1,2,3,4,5] </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Set提供了處理一系列值的方式,不過如果想給這些值添加一些附加數據則顯得力不從心,所以又提供了一種新的數據結構:Map

十、Map數據結構

​ ECMAScript 6 中的 map 類型包含一組有序的鍵值對,其中鍵和值可以是任何類型。

​ 鍵的比較結果由 Object.is() 來決定,所以你可以同時使用 5 和 “5” 做為鍵來存儲,因為它們是不同的類型。

​ 這和使用對象屬性做為值的方法大相徑庭,因為 對象的屬性會被強制轉換為字符串類型

10.1 創建Map對象和Map的基本的存取操作

  1. Map創建也是使用Map構造函數
  2. 向Map存儲鍵值對使用set(key, value);方法
  3. 可以使用get(key),來獲取指定key對應的value
<script type="text/javascript"> var map = new Map(); map.set("a", "lisi"); map.set("b", "zhangsan"); map.set("b", "zhangsan222"); // 第二次添加,新的value會替換掉舊的 console.log(map.get("a")); console.log(map.get("b")); //zhangsan222 console.log(map.get("c")); //undefined.如果key不存在,則返回undefined console.log(map.size); //2 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

10.2 Map與Set類似的3個方法

  • has(key) - 判斷給定的 key 是否在 map 中存在
  • delete(key) - 移除 map 中的 key 及對應的值
  • clear() - 移除 map 中所有的鍵值對

10.3 初始化Map

創建Map的時候也可以像Set一樣傳入數組。但是傳入的數組中必須有兩個元素,這個兩個元素分別是一個數組。

也就是傳入的實際是一個二維數組!

<script type="text/javascript"> //map接受一個二維數組 var map = new Map([ //每一個數組中,第一個是是map的可以,第二個是map的value。如果只有第一個,則值是undefined ["name", "lisi"], ["age", 20], ["sex", "nan"] ]); console.log(map.size); console.log(map.get("name")) </script> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

10.4 Map的forEach方法

<script type="text/javascript"> var map = new Map([ ["name", "李四"], ["age", 20], ["sex", "nan"] ]); /* 回調函數有函數: 參數1:鍵值對的value 參數2:鍵值對的key 參數3:map對象本身 */ map.forEach(function (value, key, ownMap) { console.log(`key=${key} ,vlue=${value}`); console.log(this); }) </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

十一、迭代器和for…of循環

11.1 循環問題

var colors = ["red", "green", "blue"]; for (var i = 0, len = colors.length; i < len; i++) { console.log(colors[i]); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

上面的代碼寫起來簡單,但是實際使用的過程中,我們需求自己去控制變量,如果有嵌套的情況下,還要控制多個變量,很容易出錯。

迭代器就是為了解決這個問題的。

11.2 什么是迭代器

​ 迭代器只是帶有特殊接口(方法)的對象。所有迭代器對象都帶有 next() 方法並返回一個包含兩個屬性的結果對象。這些屬性分別是 value 和 done,前者代表下一個位置的值,后者在沒有更多值可供迭代的時候為 true 。迭代器帶有一個內部指針,來指向集合中某個值的位置。當 next() 方法調用后,指針下一位置的值會被返回。

​ 若你在末尾的值被返回之后繼續調用 next(),那么返回的 done 屬性值為 true,value 的值則由迭代器設定。該值並不屬於數據集,而是專門為數據關聯的附加信息,如若該信息並未指定則返回 undefined 。迭代器返回的值和函數返回值有些類似,因為兩者都是返回給調用者信息的最終手段。

我們可以用ES5之前的知識手動創建一個迭代器:

function createIterator(items) { var i = 0; return { next: function() { var done = (i >= items.length); var value = !done ? items[i++] : undefined; return { done: done, value: value }; } }; } //創建一個可以在指定數組上面迭代的迭代器對象。 var iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }" // for all further calls console.log(iterator.next()); // "{ value: undefined, done: true }"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

從以上的示例來看,根據 ECMAScript 6 規范模擬實現的迭代器還是有些復雜。

幸運的是,ECMAScript 6 還提供了生成器,使得迭代器對象的創建容易了許多。

11.3 生成器函數

生成器函數就是返回迭代器的函數!

生成器函數由 function 關鍵字和之后的星號(*)標識,同時還能使用新的 yield 關鍵字。

看下面代碼:

<script type="text/javascript"> //生成器函數。 注意中間的 * 不能丟 function * createIterator() { //每個yield的后面的值表示我們迭代到的值。 yield也定義了我們迭代的順序。 yield 3; yield 4; yield 2; } var it = createIterator(); console.log(it.next().value); // 2 console.log(it.next().value); // 4 console.log(it.next().value); // 2 console.log(it.next().value); //undefined </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

迭代器函數也是函數,所以他可以像正常的函數一樣調用,但是生成器函數會自動返回一個迭代器對象。

每調用一次迭代器的next方法,如果碰到yield都會返回一個迭代到的一個對象,然后停止繼續執行,直到下次調用next方法,會從上次停止的地方繼續執行。

//這個迭代器函數返回的迭代器可以迭代傳入的數組中的所有元素。 function *createIterator(items) { for (let i = 0; i < items.length; i++) { //每調用一次next,碰到yild程序就會停止,並返回迭代到的對象 {value : items[i], done : true} yield items[i]; } } let iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }" // 進一步調用 console.log(iterator.next()); // "{ value: undefined, done: true }"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注意:

  1. yield 關鍵字只能 直接用在生成器內部 。在其它地方甚至是生成器內部的函數中使用都會拋出語法錯誤。

11.4 生成器函數表達式

你可以使用函數表達式來創建生成器,只需在 function 關鍵字和圓括號之間添加星號(*)。例如:

let createIterator = function *(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } }; let iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }" // 進一步調用 console.log(iterator.next()); // "{ value: undefined, done: true }"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注意:無法使用箭頭函數來創建生成器。

11.5 可迭代類型和for-of迭代循環

迭代器的主要工作就是迭代數據,但是不是所有的數據都是可以迭代的。

​ 與迭代器緊密相關的是,可迭代類型是指那些包含 Symbol.iterator 屬性的對象。

​ 該 symbol 類型定義了返回迭代器的函數。在 ECMAScript 6 中,所有的集合對象(數組,set 和 map)與字符串都是可迭代類型,因此它們都有默認的迭代器。可迭代類型是為了 ECMAScript6 新添加的 for-of 循環而設計的。

​ 換句話說,默認情況下只有 數組、set、Map和字符串才可以使用迭代器去迭代。 (也就可以使用for…of了)

​ for…of循環只迭代出來的元素,根本不管索引!不管索引!不管索引!重要的問題重復三遍!

使用 for…of 迭代數組:

<script type="text/javascript"> var arr = ["a", "c", "b", "d"]; for(var item of arr){ console.log(item) } </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用 for…of 迭代Set:

<script type="text/javascript"> var set = new Set(["a", "c", "b", "d"]); for(var item of set){ console.log(item) } </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用 for…of 迭代Map:

<script type="text/javascript"> var map = new Map([["name", "lisi"],["sex", "男"],["age", 20]]); map.set("aaa", "bbb") for(var item of map){ console.log(item); //注意:這里迭代到的是由key和value組成的數組。 } </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用for … of迭代字符串

<script type="text/javascript"> var s = "abcd"; for(let c of s){ console.log(c) } </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:for…of 只能迭代可以迭代的對象,對於非可迭代對象使用for…of會拋出異常

說明:以數組為例。

​ for-of 循環首先會調用 values 數組的 Symbol.iterator 方法來獲取迭代器(Symbol.iterator 方法由幕后的 JavaScript 引擎調用)。之后再調用 iterator.next() 並將結果對象中的 value 屬性值,即 1,2,3,依次賦給 num 變量。當檢測到結果對象中的 done 為 true,循環會退出,所以 num 不會被賦值為 undefined 。

​ 如果你只想簡單的迭代數組或集合中的元素,那么 for-of 循環比 for 要更好。for-of 一般不容易出錯,因為要追蹤的條件更少。所以還是把 for 循環留給復雜控制條件的需求吧。

11.6 訪問可迭代類型的默認迭代器

Symbol.iterator是可迭代類型的一個方法,調用這個方法就可以獲取到他的默認迭代器。

<script type="text/javascript"> let s = "abcd"; let it = s[Symbol.iterator](); //調用字符串的Symbol.iterator方法 console.log(it.next()); //返回迭代器迭代到的第一個對象 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

因為Symbol可以返回一個對象的默認迭代器,所以我們可以使用它來判斷一個對象是否可迭代

<script type="text/javascript"> function isIterable(object) { return typeof object[Symbol.iterator] === "function"; } console.log(isIterable([1, 2, 3])); // true console.log(isIterable("Hello")); // true console.log(isIterable(new Map())); // true console.log(isIterable(new Set())); // true console.log(isIterable({"name":"李四"})); // false。普通對象不可迭代 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

11.7 自定義可迭代類型

開發者自定義的對象默認是不可迭代類型,但是你可以為它們創建 Symbol.iterator 屬性並指定一個生成器來使這個對象可迭代。例如:

let collection = { items: [], *[Symbol.iterator]() { for (let item of this.items) { yield item; } } }; collection.items.push(1); collection.items.push(2); collection.items.push(3); for (let x of collection) { console.log(x); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

十二、類

和大多數面向對象的語言(object-oriented programming language)不同,JavaScript 在誕生之初並不支持使用類和傳統的類繼承並作為主要的定義方式來創建相似或關聯的對象。

這很令開發者困惑,而且在早於 ECMAScript 1 到 ECMAScript 5 這段時期,很多庫都創建了一些實用工具(utility)來讓 JavaScript 從表層上支持類。

盡管一些 JavaScript 開發者強烈主張該語言不需要類,但由於大量的庫都對類做了實現,ECMAScript 6 也順勢將其引入。

12.1 ES5之前的模擬的類

​ 在 ECMAScript 5 或更早的版本中,JavaScript 沒有類。和類這個概念及行為最接近的是創建一個構造函數並在構造函數的原型上添加方法,這種實現也被稱為自定義的類型創建,例如:

function PersonType(name) { this.name = name; } PersonType.prototype.sayName = function() { console.log(this.name); }; let person = new PersonType("Nicholas"); person.sayName(); // 輸出 "Nicholas" console.log(person instanceof PersonType); // true console.log(person instanceof Object); // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

說明:

前面的PersonType我們以前一直叫做構造函數,其實他就是一個類型,因為他確實表示了一種類型。

12.2 ES6中基本的類聲明

在ES6直接借鑒其他語言,引入了類的概念。所以再實現上面那種模擬 的類就容易了很多。

//class關鍵字必須是小寫。 后面就是跟的類名 class PersonClass { // 等效於 PersonType 構造函數。 constructor(name) { //這個表示類的構造函數。constuctor也是關鍵字必須小寫。 this.name = name; //創建屬性。 也叫當前類型的自有屬性。 } // 等效於 PersonType.prototype.sayName. 這里的sayName使用了我們前面的簡寫的方式。 sayName() { console.log(this.name); } } let person = new PersonClass("Nicholas"); person.sayName(); // 輸出 "Nicholas" console.log(person instanceof PersonClass); // true console.log(person instanceof Object); // true console.log(typeof PersonClass); // "function" console.log(typeof PersonClass.prototype.sayName); // "function"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

說明:

  1. 自有屬性:屬性只出現在實例而不是原型上,而且只能由構造函數和方法來創建。在本例中,name 就是自有屬性。我建議 盡可能的將所有自有屬性創建在構造函數中,這樣當查找屬性時可以做到一目了然。
  2. 類聲明只是上例中自定義類型的語法糖。PersonClass 聲明實際上創建了一個行為和 constructor 方法相同的構造函數,這也是 typeof PersonClass 返回 “function” 的原因。sayName() 在本例中作為 PersonClass.prototype 的方法,和上個示例中 sayName() 和 PersonType.prototype 關系一致。這些相似度允許你混合使用自定義類型和類而不需要糾結使用方式。

雖然類和以前的使用構造函數+原型的方式很像,但是還是有一些不太相同的地方,而且要牢記

  1. 類聲明和函數定義不同,類的聲明是不會被提升的。類聲明的行為和 let 比較相似,所以當執行流作用到類聲明之前類會存在於暫存性死區(temporal dead zone)內。
  2. 類聲明中的代碼自動運行在嚴格模式下,同時沒有任何辦法可以手動切換到非嚴格模式。
  3. 所有的方法都是不可枚舉的(non-enumerable),這和自定義類型相比是個顯著的差異,因為后者需要使用 Object.defineProperty() 才能定義不可枚舉的方法。
  4. 所有的方法都不能使用 new 來調用,因為它們沒有內部方法 [[Construct]]。
  5. 不使用 new 來調用類構造函數會拋出錯誤。也就是 必須使用new 類() 的方式使用
  6. 試圖在類的方法內部重寫類名的行為會拋出錯誤。(因為在類的內部,類名是作為一個常量存在的)

12.2 匿名類表達式

函數有函數表達式,類也有類表達式。

類表達式的功能和前面的類的聲明是一樣的。

let PersonClass = class { // 等效於 PersonType 構造函數 constructor(name) { this.name = name; } // 等效於 PersonType.prototype.sayName sayName() { console.log(this.name); } }; let person = new PersonClass("Nicholas"); person.sayName(); // 輸出 "Nicholas" console.log(person instanceof PersonClass); // true console.log(person instanceof Object); // true console.log(typeof PersonClass); // "function" console.log(typeof PersonClass.prototype.sayName); // "function"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

12.3 具名類表達式


let PersonClass = class PersonClass2{ // 等效於 PersonType 構造函數 constructor(name) { this.name = name; } // 等效於 PersonType.prototype.sayName sayName() { console.log(this.name); } }; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

注意:具名類表達式中PersonClass2這個類名只能在類的內部訪問到,在外面是訪問不到的.

12.4 作為一等公民的類型

在JavaScript中,函數是作為一等公民存在的。(也叫一等函數)。

類也是一等公民。

  1. 類可以作為參數傳遞
function createObject(classDef) { return new classDef(); } let obj = createObject(class { sayHi() { console.log("Hi!"); } }); obj.sayHi(); // "Hi!"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 立即調用類構造函數,創建單例
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }("Nicholas"); person.sayName(); // "Nicholas"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

12.5 動態計算類成員的命名

類的成員,也可以像我們前面的對象的屬性一樣可以動態計算.( 使用[ ] 來計算)

let methodName = "sayName"; class PersonClass { constructor(name) { this.name = name; } [methodName]() { console.log(this.name); } } let me = new PersonClass("Nicholas"); me.sayName(); // "Nicholas"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

12.6 靜態成員

在ES5中,我們可以直接給構造函數添加屬性或方法來模擬靜態成員。

function PersonType(name) { this.name = name; } // 靜態方法。 直接添加到構造方法上。 (其實是把構造函數當做一個普通的對象來用。) PersonType.create = function(name) { return new PersonType(name); }; // 實例方法 PersonType.prototype.sayName = function() { console.log(this.name); }; var person = PersonType.create("Nicholas");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在上面的create方法在其他語言中一般都是作為靜態方法來使用的。

下面高能,請注意:

ECMAScript 6 的類通過在方法之前使用正式的 static 關鍵字簡化了靜態方法的創建。例如,下例中的類和上例相比是等效的:

class PersonClass { // 等效於 PersonType 構造函數 constructor(name) { this.name = name; } // 等效於 PersonType.prototype.sayName sayName() { console.log(this.name); } // 等效於 PersonType.create。 static create(name) { return new PersonClass(name); } } let person = PersonClass.create("Nicholas");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注意:靜態成員通過實例對象不能訪問,只能通過類名訪問!!!

通過和ES5模擬靜態方法的例子你應該知道為啥了吧

12.7 ES6中的繼承

在ES6之前要完成繼承,需要寫很多的代碼。看下面的繼承的例子:

<script type="text/javascript"> function Father(name) { this.name = name; } Father.prototype.sayName = function () { console.log(this.name); } function Son(name,age) { Father.call(this, name); this.age = age; } Son.prototype = new Father(); Son.prototype.constructor = Son; Son.prototype.sayAge = function () { console.log(this.age); } var son1 = new Son("兒子", 20); son1.sayAge(); //20 son1.sayName(); //兒子 </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

12.7.1 繼承的基本寫法

如果在ES6通過類的方式完成繼承就簡單了很多。

需要用到一個新的關鍵字:extends

<script type="text/javascript"> class Father{ constructor(name){ this.name = name; } sayName(){ console.log(this.name); } } class Son extends Father{ //extents后面跟表示要繼承的類型 constructor(name, age){ super(name); //相當於以前的:Father.call(this, name); this.age = age; } //子類獨有的方法 sayAge(){ console.log(this.age); } } var son1 = new Son("李四", 30); son1.sayAge(); son1.sayName(); console.log(son1 instanceof Son); // true console.log(son1 instanceof Father); //true </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

這種繼承方法,和我們前面提到的構造函數+原型的繼承方式本質是一樣的。但是寫起來更簡單,可讀性也更好。

關於super的使用,有幾點需要注意:

  1. 你只能在派生類中使用 super(),否則(沒有使用 extends 的類或函數中使用)一個錯誤會被拋出。
  2. 你必須在構造函數的起始位置調用 super(),因為它會初始化 this。任何在 super() 之前訪問 this 的行為都會造成錯誤。也即是說super()必須放在構造函數的首行。
  3. 在類構造函數中,唯一能避免調用 super() 的辦法是返回一個對象。

12.7.2 在子類中屏蔽父類的方法

如果在子類中聲明與父類中的同名的方法,則會覆蓋父類的方法。(這種情況在其他語言中稱之為 方法的覆寫、重寫 )

<script type="text/javascript"> class Father{ constructor(name){ this.name = name; } sayName(){ console.log(this.name); } } class Son extends Father{ //extents后面跟表示要繼承的類型 constructor(name, age){ super(name); //相當於以前的:Father.call(this, name); this.age = age; } //子類獨有的方法 sayAge(){ console.log(this.age); } //子類中的方法會屏蔽到父類中的同名方法。 sayName(){ super.syaName(); //調用被覆蓋的父類中的方法。 console.log("我是子類的方法,我屏蔽了父類:" + name); } } var son1 = new Son("李四", 30); son1.sayAge(); son1.sayName(); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

如果在子類中又確實需要調用父類中被覆蓋的方法,可以通過super.方法()來完成。

注意:

  1. 如果是調用構造方法,則super不要加點,而且必須是在子類構造方法的第一行調用父類的構造方法
  2. 普通方法調用需要使用super.父類的方法() 來調用。

12.7.3 靜態方法也可以繼承

<script type="text/javascript"> class Father{ static foo(){ console.log("我是父類的靜態方法"); } } class Son extends Father{ } Son.foo(); //子類也繼承了父類的靜態方法。 這種方式調用和直接通過父類名調用時一樣的。 </script>


原文地址:http://blog.csdn.net/u012468376/article/details/54565068


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM