ECMAScript6介紹及環境搭建

一、ES6簡介

1.一、什麼是ES6

ECMAScript 6.0(如下簡稱 ES6)是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式發佈了。它的目標,是使得 JavaScript 語言能夠用來編寫複雜的大型應用程序,成爲企業級開發語言。javascript

1.二、ECMAScript和JavaScript的關係

一個常見的問題是,ECMAScript 和 JavaScript 究竟是什麼關係?html

要講清楚這個問題,須要回顧歷史。1996 年 11 月,JavaScript 的創造者 Netscape 公司,決定將 JavaScript 提交給標準化組織 ECMA,但願這種語言可以成爲國際標準。次年,ECMA 發佈 262 號標準文件(ECMA-262)的初版,規定了瀏覽器腳本語言的標準,並將這種語言稱爲 ECMAScript,這個版本就是 1.0 版。該標準從一開始就是針對 JavaScript 語言制定的,可是之因此不叫 JavaScript,有兩個緣由。一是商標,Java 是 Sun 公司的商標,根據受權協議,只有 Netscape 公司能夠合法地使用 JavaScript 這個名字,且 JavaScript 自己也已經被 Netscape 公司註冊爲商標。二是想體現這門語言的制定者是 ECMA,不是 Netscape,這樣有利於保證這門語言的開放性和中立性。所以,ECMAScript 和 JavaScript 的關係是,前者是後者的規格,後者是前者的一種實現(另外的 ECMAScript 方言還有 JScript 和 ActionScript)。平常場合,這兩個詞是能夠互換的。前端

1.三、爲何要學習ES6?

這個問題能夠轉換一種問法,就是學完es6會給咱們的開發帶來什麼樣便利?chrome解釋javascript的引擎叫作V8,有一我的把V8引擎轉移到了服務器,因而服務器端也能夠寫javascript,這種在服務器端運行的js語言,就是Node.js。Node.js一經問世,它優越的性能就表現了出了,不少基於nodejs的web框架也應運而生,express就是之一,隨之而來的就是全棧MEAN mogoDB,Express,Vue.js,Node.js開發,javaScript愈來愈多的使用到web領域的各個角落,js能作的事情也愈來愈多。Babel是一個普遍使用的ES6轉碼器,能夠將ES6代碼轉爲ES5代碼,從而在現有環境執行。這意味着,你能夠用ES6的方式編寫程序,又不用擔憂現有環境是否支持。nodejs是一種開發趨勢,Vue.js這種前端框架是一種開發趨勢,ES6被普及使用也是趨勢。目前一些前端框架都在使用ES6語法,例如Vue、React、D3等等,因此ES6也是學習好前端框架的基礎。java

二、ES6環境搭建

因爲有些低版本的瀏覽器還不支持ES6的語法,因此在不使用框架的狀況下,須要將ES6語法轉換爲ES5語法。node

2.一、前期準備

先建立一個項目,項目中有兩個文件夾,src和dist,一個html文件git

src:將編寫的ES6的js文件放到此文件夾中(這裏是index.js文件)程序員

dist:將經過Babel編譯成的ES5的js文件放到此文件中(這裏是index.js文件)es6

html:注意:將dist中編譯好的文件引入到HTML文件中,而不是src中的js文件github

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	<script src="./dist/index.js"></script>
</head>
<body>
	Hello ES6
</body>
</html>

2.二、ES6環境搭建

第一步web

在src目錄下,新建index.js文件。這個文件很簡單,咱們只做一個a變量的聲明,並用console.log()打印出來。

let a = 1;
console.log(a);

第二步

在項目的根目錄初始化項目並生成package.json文件(能夠根據本身的需求進行修改)

cnpm init -y
{
    "name": "es6",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
     },
    "keywords": [],
    "author": "",
    "license": "ISC"
}

第三步

安裝Babel插件(將ES6語法轉換爲ES5)

cnpm install -g babel-cli

第四步

固然如今還不能正常轉換,還須要安裝ES5所需的一個包

cnpm install --save-dev babel-preset-es2015 babel-cli 
## 安裝完成後,package.json會有所變化
{
    "name": "es6",
     "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-es2015": "^6.24.1"
    }
}

第五步:

在項目的根目錄添加一個 .babelrc 文件,並添加內容

{
    "presets":[
        "es2015"
    ],
    "plugins": []
}

在windows系統中建立.babelrc文件的方法

方法一:根目錄下,建立「.babelrc.」文件名就能夠了!(先後共兩個點)

方法二:cmd進入根目錄,輸入「type null>.babelrc」,回車便可!

第六步:

安裝完成後咱們能夠經過命令進行轉換

babel src/index.js -o dist/index.js

第七步:

能夠將命令進行簡化(package.json進行配置)

"scripts": {
    "test": "echo "Error: no test specified" && exit 1"
},

修改成:

{
  	"name": "es6",
  	"version": "1.0.0",
  	"description": "",
  	"main": "index.js",
  	"scripts": {
        "test": "babel src/index.js -o dist/index.js"
  	},
  	"keywords": [],
  	"author": "",
  	"license": "ISC",
  	"devDependencies": {
		"babel-cli": "^6.26.0",
		"babel-preset-es2015": "^6.24.1"
  	}
}

而後咱們能夠經過下面命令轉義代碼:

npm run test

三、let與const

ES2015(ES6) 新增長了兩個重要的 JavaScript 關鍵字: letconst

let 聲明的變量只在 let 命令所在的代碼塊內有效,const 聲明一個只讀的常量,一旦聲明,常量的值就不能改變。

3.一、let命令

let命令有如下特色:

(1)代碼塊內有效

ES2015(ES6) 新增長了兩個重要的 JavaScript 關鍵字: letconst。let 聲明的變量只在 let 命令所在的代碼塊內有效,const 聲明一個只讀的常量,一旦聲明,常量的值就不能改變。

{
    let a = 1;
    var b = 2;
    console.log(a);//輸出1
    console.log(b);//輸出2
}
console.log(a);//報錯 ReferenceError: a is not defined
console.log(b);//輸出2

(2)不能重複聲明

let 只能聲明一次 var 能夠聲明屢次:

let a = 1;
let a = 2;//報錯 Identifier 'a' has already been declared
var b = 3;
var b = 4;
console.log(a);
console.log(b);//輸出4

for 循環計數器很適合用 let

for (var i = 0; i < 10; i++) {
  setTimeout(function(){
    console.log(i);
  })
}
// 輸出十個 10
for (let j = 0; j < 10; j++) {
  setTimeout(function(){
    console.log(j);
  })
}
// 輸出 0123456789

變量 i 是用 var 聲明的,在全局範圍內有效,因此全局中只有一個變量 i, 每次循環時,setTimeout 定時器裏面的 i 指的是全局變量 i ,而循環裏的十個 setTimeout 是在循環結束後才執行,因此此時的 i 都是 10。

變量 j 是用 let 聲明的,當前的 j 只在本輪循環中有效,每次循環的 j 其實都是一個新的變量,因此 setTimeout 定時器裏面的 j 實際上是不一樣的變量,即最後輸出 12345。(若每次循環的變量 j 都是從新聲明的,如何知道前一個循環的值?這是由於 JavaScript 引擎內部會記住前一個循環的值)。

(3)不存在變量提高

let 不存在變量提高,var 會變量提高:

console.log(a);  //ReferenceError: a is not defined
let a = "apple";

console.log(b);  //undefined
var b = "banana";

變量 b 用 var 聲明存在變量提高,因此當腳本開始運行的時候,b 已經存在了,可是尚未賦值,因此會輸出 undefined。變量 a 用 let 聲明不存在變量提高,在聲明變量 a 以前,a 不存在,因此會報錯。

(4)暫時性死區

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

var tmp = 123;

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

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

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

總之,在代碼塊內,使用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的「死區」。

「暫時性死區」也意味着typeof再也不是一個百分之百安全的操做。

typeof x; // ReferenceError
let x;

另外,下面的代碼也會報錯,與var的行爲不一樣。

// 不報錯
var x = x;

// 報錯
let x = x;
// ReferenceError: x is not defined

上面代碼報錯,也是由於暫時性死區。使用let聲明變量時,只要變量在尚未聲明完成前使用,就會報錯。上面這行就屬於這個狀況,在變量x的聲明語句尚未執行完成前,就去取x的值,致使報錯」x 未定義「。

ES6 規定暫時性死區和let、const語句不出現變量提高,主要是爲了減小運行時錯誤,防止在變量聲明前就使用這個變量,從而致使意料以外的行爲。這樣的錯誤在 ES5 是很常見的,如今有了這種規定,避免此類錯誤就很容易了。

總之,暫時性死區的本質就是,只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。

3.二、const命令

const 聲明一個只讀變量,聲明以後不容許改變。意味着,一旦聲明必須初始化,不然會報錯。

基本用法:

const PI = "3.1415926";
PI  // 3.1415926

const MY_AGE;  // 報錯 SyntaxError: Missing initializer in const declaration

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

const foo;
// 報錯 SyntaxError: Missing initializer 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;

暫時性死區:

var PI = "a";
if(true){
  console.log(PI);  //報錯 ReferenceError: PI is not defined
  const PI = "3.1415926";
}

ES6 明確規定,代碼塊內若是存在 let 或者 const,代碼塊會對這些命令聲明的變量從塊的開始就造成一個封閉做用域。代碼塊內,在聲明變量 PI 以前使用它會報錯。

注意要點

const 如何作到變量在聲明初始化以後不容許改變的?其實 const 其實保證的不是變量的值不變,而是保證變量指向的內存地址所保存的數據不容許改動。此時,你可能已經想到,簡單類型和複合類型保存值的方式是不一樣的。是的,對於簡單類型(數值 number、字符串 string 、布爾值 boolean),值就保存在變量指向的那個內存地址,所以 const 聲明的簡單類型變量等同於常量。而複雜類型(對象 object,數組 array,函數 function),變量指向的內存地址實際上是保存了一個指向實際數據的指針,因此 const 只能保證指針是固定的,至於指針指向的數據結構變不變就沒法控制了,因此使用 const 聲明覆雜類型對象時要慎重。

四、ES6解構賦值

4.一、解構賦值概述

解構賦值是對賦值運算符的擴展。

它是一種針對數組或者對象進行模式匹配,而後對其中的變量進行賦值。在代碼書寫上簡潔且易讀,語義更加清晰明瞭;也方便了複雜對象中數據字段獲取。

4.二、解構模型

ES6 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。

在解構中,有下面兩部分參與:

解構的源,解構賦值表達式的右邊部分;

解構目標,解構賦值表達式的左邊部分;

在ES5中,爲變量賦值只能直接指定變量的值:

let a = 1;
let b = 2;

在ES6中,變量賦值容許寫成:

let [a,b,c] = [1,2,3];
console.log(a); // 輸出1
console.log(b); // 輸出2
console.log(c); // 輸出3

面代碼表示,能夠從數組中提取值,按照對應位置,對變量賦值。

本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。

4.三、數組的解構賦值

基本用法

let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3

可嵌套

let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3

可忽略

let [a, , b] = [1, 2, 3];
// a = 1
// b = 3

不徹底解構

let [a = 1, b] = []; // a = 1, b = undefined

若是解構不成功,變量的值就等於undefined。

let [foo] = [];
let [bar, foo] = [1];

以上兩種狀況都屬於解構不成功,foo的值都會等於undefined。

另外一種狀況是不徹底解構,即等號左邊的模式,只匹配一部分的等號右邊的數組。這種狀況下,解構依然能夠成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

上面兩個例子,都屬於不徹底解構,可是能夠成功。

若是等號的右邊不是數組(或者嚴格地說,不是可遍歷的結構),那麼將會報錯。

// 報錯
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

上面的語句都會報錯,由於等號右邊的值,要麼轉爲對象之後不具有 Iterator 接口(前五個表達式),要麼自己就不具有 Iterator 接口(最後一個表達式)。

剩餘運算符

let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]

字符串

在數組的解構中,解構的目標若爲可遍歷對象,皆可進行解構賦值。可遍歷對象即實現 Iterator 接口的數據。

let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'

解構默認值

解構賦值容許指定默認值。

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

當解構模式有匹配結果,且匹配結果是 undefined 時,會觸發默認值做爲返回結果。

let [a = 2] = [undefined]; // a = 2

注意,ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。因此,只有當一個數組成員嚴格等於undefined,默認值纔會生效。

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null

上面代碼中,若是一個數組成員是null,默認值就不會生效,由於null不嚴格等於undefined。

若是默認值是一個表達式,那麼這個表達式是惰性求值的,即只有在用到的時候,纔會求值。

function f() {
  console.log('aaa');
}

let [x = f()] = [1];

上面代碼中,由於x能取到值,因此函數f根本不會執行。上面的代碼其實等價於下面的代碼。

let x;
if ([1][0] === undefined) {
  x = f();
} else {
  x = [1][0];
}

默認值能夠引用解構賦值的其餘變量,但該變量必須已經聲明。

let [a = 3, b = a] = []; // a = 3, b = 3
let [a = 3, b = a] = [1];// a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
let [a = b, b = 1] = []; // ReferenceError: y is not defined

上述代碼解釋:

  • a 與 b 匹配結果爲 undefined ,觸發默認值:a = 3; b = a =3;
  • a 正常解構賦值,匹配結果:a = 1,b 匹配結果 undefined ,觸發默認值:b = a =1;
  • a 與 b 正常解構賦值,匹配結果:a = 1,b = 2;
  • 上面最後一個表達式之因此會報錯,是由於x用y作默認值時,y尚未聲明。

4.四、對象的解構賦值

(1)基本用法

解構不只能夠用於數組,還能夠用於對象。

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

上面代碼的第一個例子,等號左邊的兩個變量的次序,與等號右邊兩個同名屬性的次序不一致,可是對取值徹底沒有影響。第二個例子的變量沒有對應的同名屬性,致使取不到值,最後等於undefined。

若是解構失敗,變量的值等於undefined。

let {foo} = {bar: 'baz'};
foo // undefined

上面代碼中,等號右邊的對象沒有foo屬性,因此變量foo取不到值,因此等於undefined。

對象的解構賦值,能夠很方便地將現有對象的方法,賦值到某個變量。

// 例一
let { log, sin, cos } = Math;

// 例二
const { log } = console;
log('hello') // hello

上面代碼的例一將Math對象的對數、正弦、餘弦三個方法,賦值到對應的變量上,使用起來就會方便不少。例二將console.log賦值到log變量。

若是變量名與屬性名不一致,必須寫成下面這樣。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

這實際上說明,對象的解構賦值是下面形式的簡寫。

let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

也就是說,對象的解構賦值的內部機制,是先找到同名屬性,而後再賦給對應的變量。真正被賦值的是後者,而不是前者。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

上面代碼中,foo是匹配的模式,baz纔是變量。真正被賦值的是變量baz,而不是模式foo。

(2)嵌套對象的解構賦值

與數組同樣,解構也能夠用於嵌套結構的對象。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

注意,這時p是模式,不是變量,所以不會被賦值。若是p也要做爲變量賦值,能夠寫成下面這樣。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]

下面是另外一個例子。

4.五、解構賦值注意事項

(1)若是要將一個已經聲明的變量用於解構賦值,必須很是當心。

// 錯誤的寫法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代碼的寫法會報錯,由於 JavaScript 引擎會將{x}理解成一個代碼塊,從而發生語法錯誤。只有不將大括號寫在行首,避免 JavaScript 將其解釋爲代碼塊,才能解決這個問題。

// 正確的寫法
let x;
({x} = {x: 1});

上面代碼將整個解構賦值語句,放在一個圓括號裏面,就能夠正確執行。關於圓括號與解構賦值的關係,參見下文。

(2)解構賦值容許等號左邊的模式之中,不放置任何變量名。所以,能夠寫出很是古怪的賦值表達式。

({} = [true, false]);
({} = 'abc');
({} = []);

上面的表達式雖然毫無心義,可是語法是合法的,能夠執行。

(3)因爲數組本質是特殊的對象,所以能夠對數組進行對象屬性的解構。

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

上面代碼對數組進行對象解構。數組arr的0鍵對應的值是1,[arr.length - 1]就是2鍵,對應的值是3。方括號這種寫法,屬於「屬性名錶達式」。

4.六、解構賦值的用途

變量的解構賦值用途不少。

(1)交換變量的值

let x = 1;
let y = 2;

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

上面代碼交換變量x和y的值,這樣的寫法不只簡潔,並且易讀,語義很是清晰。

(2)從函數返回多個值

函數只能返回一個值,若是要返回多個值,只能將它們放在數組或對象裏返回。有了解構賦值,取出這些值就很是方便。

// 返回一個數組

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一個對象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

(3)函數參數的定義

解構賦值能夠方便地將一組參數與變量名對應起來。

// 參數是一組有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 參數是一組無次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

(4)提取JSON數據

解構賦值對提取 JSON 對象中的數據,尤爲有用。

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

上面代碼能夠快速提取 JSON 數據的值。

(5)函數參數的默認值

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
} = {}) {
  // ... do stuff
};

指定參數的默認值,就避免了在函數體內部再寫var foo = config.foo || ‘default foo’;這樣的語句。

(6)遍歷Map結構

任何部署了 Iterator 接口的對象,均可以用for…of循環遍歷。Map 結構原生支持 Iterator 接口,配合變量的解構賦值,獲取鍵名和鍵值就很是方便。

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

若是隻想獲取鍵名,或者只想獲取鍵值,能夠寫成下面這樣。

// 獲取鍵名
for (let [key] of map) {
  // ...
}

// 獲取鍵值
for (let [,value] of map) {
  // ...
}

(7)輸入模塊的指定方法

加載模塊時,每每須要指定輸入哪些方法。解構賦值使得輸入語句很是清晰。

const { SourceMapConsumer, SourceNode } = require("source-map");

五、字符串、函數、數組、對象的擴展

5.一、模板字符串

傳統的 JavaScript 語言,輸出模板一般是這樣寫的(下面使用了 jQuery 的方法)。

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面這種寫法至關繁瑣不方便,ES6 引入了模板字符串解決這個問題。

$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);

模板字符串(template string)是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is not legal.`

console.log(`string text line 1 string text line 2`);

// 字符串中嵌入變量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

上面代碼中的模板字符串,都是用反引號表示。

轉義符號

若是在模板字符串中須要使用反引號,則前面要用反斜槓轉義。

let greeting = `\`Yo\` World!`;

多行字符串

若是使用模板字符串表示多行字符串,全部的空格和縮進都會被保留在輸出之中。

$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `);

上面代碼中,全部模板字符串的空格和換行,都是被保留的,好比 <ul> 標籤前面會有一個換行。若是你不想要這個換行,可使用 trim 方法消除它。

$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());

插入變量

模板字符串中嵌入變量,須要將變量名寫在${}之中。

function authorize(user, action) {
  if (!user.hasPrivilege(action)) {
    throw new Error(
      // 傳統寫法爲
      // 'User '
      // + user.name
      // + ' is not authorized to do '
      // + action
      // + '.'
      `User ${user.name} is not authorized to do ${action}.`);
  }
}

插入表達式

大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。

let x = 1;
let y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"

調用函數

模板字符串之中還能調用函數。

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

若是大括號中的值不是字符串,將按照通常的規則轉爲字符串。好比,大括號中是一個對象,將默認調用對象的 toString 方法。

若是模板字符串中的變量沒有聲明,將報錯。

// 變量place沒有聲明
let msg = `Hello, ${place}`;
// 報錯

因爲模板字符串的大括號內部,就是執行 JavaScript 代碼,所以若是大括號內部是一個字符串,將會原樣輸出。

`Hello ${'World'}`
// "Hello World"

注意要點

模板字符串中的換行和空格都是會被保留的

innerHtml = `<ul> <li>menu</li> <li>mine</li> </ul> `;
console.log(innerHtml);
// 輸出
<ul>
 <li>menu</li>
 <li>mine</li>
</ul>

5.二、字符串擴展方法

(1)子串的識別

ES6 以前判斷字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的識別方法。

  • includes():返回布爾值,判斷是否找到參數字符串。
  • startsWith():返回布爾值,判斷參數字符串是否在原字符串的頭部。
  • endsWith():返回布爾值,判斷參數字符串是否在原字符串的尾部。

以上三個方法均可以接受兩個參數,須要搜索的字符串,和可選的搜索起始位置索引。

let s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

這三個方法都支持第二個參數,表示開始搜索的位置。

let s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

上面代碼表示,使用第二個參數 n 時, endsWith 的行爲與其餘兩個方法有所不一樣。它針對前 n 個字符,而其餘兩個方法針對從第 n 個位置直到字符串結束。

注意點:

  • 這三個方法只返回布爾值,若是須要知道子串的位置,仍是得用 indexOf 和 lastIndexOf 。
  • 這三個方法若是傳入了正則表達式而不是字符串,會拋出錯誤。而 indexOf 和 lastIndexOf 這兩個方法,它們會將正則表達式轉換爲字符串並搜索它。

(2)字符串重複

repeat():返回新的字符串,表示將字符串重複指定次數返回。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

參數若是是小數,會被向下取整。

'na'.repeat(2.9) // "nana"

若是 repeat 的參數是負數或者 Infinity ,會報錯。

'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError

可是,若是參數是 0 到-1 之間的小數,則等同於 0,這是由於會先進行取整運算。0 到-1 之間的小數,取整之後等於 -0repeat 視同爲 0。

'na'.repeat(-0.9) // ""

參數 NaN 等同於 0。

'na'.repeat(NaN) // ""

若是 repeat 的參數是字符串,則會先轉換成數字。

'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

(3)字符串補全

ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。

  • padStart:返回新的字符串,表示用參數字符串從頭部(左側)補全原字符串。
  • padEnd:返回新的字符串,表示用參數字符串從尾部(右側)補全原字符串。

以上兩個方法接受兩個參數,第一個參數是指定生成的字符串的最小長度,第二個參數是用來補全的字符串。若是沒有指定第二個參數,默認用空格填充。

console.log("h".padStart(5,"o"));  // "ooooh"
console.log("h".padEnd(5,"o"));    // "hoooo"
console.log("h".padStart(5));      // " h"

console.log('x'.padStart(5, 'ab')); // 'ababx'
console.log('x'.padStart(4, 'ab')); // 'abax'

console.log('x'.padEnd(5, 'ab')); // 'xabab'
console.log('x'.padEnd(4, 'ab')); // 'xaba'

上面代碼中, padStart()padEnd() 一共接受兩個參數,第一個參數是字符串補全生效的最大長度,第二個參數是用來補全的字符串。

若是指定的長度小於或者等於原字符串的長度,則返回原字符串:

console.log("hello".padStart(5,"A"));  // "hello"

若是原字符串加上補全字符串長度大於指定長度,則截去超出位數的補全字符串:

console.log("hello".padEnd(10,",world!"));  // "hello,worl"

若是省略第二個參數,默認使用空格補全長度。

console.log('x'.padStart(4)); // ' x'
console.log('x'.padEnd(4)); // 'x '

padStart()的常見用途是爲數值補全指定位數。下面代碼生成 10 位的數值字符串。

console.log('1'.padStart(10, '0')); // "0000000001"
console.log('12'.padStart(10, '0')); // "0000000012"
console.log('123456'.padStart(10, '0')); // "0000123456"

另外一個用途是提示字符串格式。

console.log('12'.padStart(10, 'YYYY-MM-DD')); // "YYYY-MM-12"
console.log('09-12'.padStart(10, 'YYYY-MM-DD')); // "YYYY-09-12"

(4)消除空格

ES6對字符串實例新增了 trimStart()trimEnd() 這兩個方法。它們的行爲與 trim() 一致,trimStart() 消除字符串頭部的空格,trimEnd() 消除尾部的空格。它們返回的都是新字符串,不會修改原始字符串。

const s = ' abc ';

s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"

上面代碼中,trimStart() 只消除頭部的空格,保留尾部的空格。trimEnd() 也是相似行爲。

除了空格鍵,這兩個方法對字符串頭部(或尾部)的 tab 鍵、換行符等不可見的空白符號也有效。

瀏覽器還部署了額外的兩個方法,trimLeft()trimStart() 的別名,trimRight()trimEnd() 的別名。

5.三、函數的擴展

(1)默認值

ES6 以前,不能直接爲函數的參數指定默認值,只能採用變通的方法。

function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

上面代碼檢查函數log的參數y有沒有賦值,若是沒有,則指定默認值爲World。這種寫法的缺點在於,若是參數y賦值了,可是對應的布爾值爲false,則該賦值不起做用。就像上面代碼的最後一行,參數y等於空字符,結果被改成默認值。

爲了不這個問題,一般須要先判斷一下參數y是否被賦值,若是沒有,再等於默認值。

if (typeof y === 'undefined') {
  y = 'World';
}

ES6 容許爲函數的參數設置默認值,即直接寫在參數定義的後面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

能夠看到,ES6 的寫法比 ES5 簡潔許多,並且很是天然。下面是另外一個例子。

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}

const p = new Point();
p // { x: 0, y: 0 }

除了簡潔,ES6 的寫法還有兩個好處:首先,閱讀代碼的人,能夠馬上意識到哪些參數是能夠省略的,不用查看函數體或文檔;其次,有利於未來的代碼優化,即便將來的版本在對外接口中,完全拿掉這個參數,也不會致使之前的代碼沒法運行。

參數變量是默認聲明的,因此不能用letconst再次聲明。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

上面代碼中,參數變量x是默認聲明的,在函數體中,不能用letconst再次聲明,不然會報錯。

使用參數默認值時,函數不能有同名參數。

// 不報錯
function foo(x, x, y) {
  // ...
}

// 報錯
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

另外,一個容易忽略的地方是,參數默認值不是傳值的,而是每次都從新計算默認值表達式的值。也就是說,參數默認值是惰性求值的。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代碼中,參數p的默認值是x + 1。這時,每次調用函數foo,都會從新計算x + 1,而不是默認p等於 100。

(2)不定參數

不定參數用來表示不肯定參數個數,形如,…變量名,由…加上一個具名參數標識符組成。具名參數只能放在參數組的最後,而且有且只有一個不定參數。

基本用法

function f(...values){
    console.log(values.length);
}
f(1,2);      //2
f(1,2,3,4);  //4

(3)箭頭函數

箭頭函數提供了一種更加簡潔的函數書寫方式。基本語法是:

參數 => 函數體

基本用法:

var f = v => v;
//等價於
var f = function(a){
 return a;
}
f(1);  //1

當箭頭函數沒有參數或者有多個參數,要用 () 括起來。

var f = (a,b) => a+b;
f(6,2);  //8

當箭頭函數函數體有多行語句,用 {} 包裹起來,表示代碼塊,當只有一行語句,而且須要返回結果時,能夠省略 {} , 結果會自動返回。

var f = (a,b) => {
 let result = a+b;
 return result;
}
f(6,2);  // 8

當箭頭函數要返回對象的時候,爲了區分於代碼塊,要用 () 將對象包裹起來

// 報錯
var f = (id,name) => {id: id, name: name};
f(6,2);  // SyntaxError: Unexpected token :
 
// 不報錯
var f = (id,name) => ({id: id, name: name});
f(6,2);  // {id: 6, name: 2}

注意點:沒有 this、super、arguments 和 new.target 綁定。

var func = () => {
  // 箭頭函數裏面沒有 this 對象,
  // 此時的 this 是外層的 this 對象,即 Window 
  console.log(this)
}
func(55)  // Window 
 
var func = () => {    
  console.log(arguments)
}
func(55);  // ReferenceError: arguments is not defined

箭頭函數體中的 this 對象,是定義函數時的對象,而不是使用函數時的對象。

function fn(){
  setTimeout(()=>{
    // 定義時,this 綁定的是 fn 中的 this 對象
    console.log(this.a);
  },0)
}
var a = 20;
// fn 的 this 對象爲 {a: 19}
fn.call({a: 18});  // 18

不能夠做爲構造函數,也就是不能使用 new 命令,不然會報錯

5.四、數組的擴展

(1)擴展運算符

擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

該運算符主要用於函數調用。

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42

上面代碼中,array.push(...items)add(...numbers)這兩行,都是函數的調用,它們都使用了擴展運算符。該運算符將一個數組,變爲參數序列。

(2)擴展運算符的應用

複製數組

數組是複合的數據類型,直接複製的話,只是複製了指向底層數據結構的指針,而不是克隆一個全新的數組。

const a1 = [1, 2];
const a2 = a1;

a2[0] = 2;
a1 // [2, 2]

上面代碼中,a2並非a1的克隆,而是指向同一份數據的另外一個指針。修改a2,會直接致使a1的變化。

ES5 只能用變通方法來複制數組。

const a1 = [1, 2];
const a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]

上面代碼中,a1會返回原數組的克隆,再修改a2就不會對a1產生影響。

擴展運算符提供了複製數組的簡便寫法。

const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;

上面的兩種寫法,a2都是a1的克隆。

合併數組

擴展運算符提供了數組合並的新寫法。

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合併數組
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合併數組
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

不過,這兩種方法都是淺拷貝,使用的時候須要注意。

const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];

const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];

a3[0] === a1[0] // true
a4[0] === a1[0] // true

上面代碼中,a3a4是用兩種不一樣方法合併而成的新數組,可是它們的成員都是對原數組成員的引用,這就是淺拷貝。若是修改了原數組的成員,會同步反映到新數組。

(3)數組實例的find()和findIndex()

數組實例的find方法,用於找出第一個符合條件的數組成員。它的參數是一個回調函數,全部數組成員依次執行該回調函數,直到找出第一個返回值爲true的成員,而後返回該成員。若是沒有符合條件的成員,則返回undefined

[1, 4, -5, 10].find((n) => n < 0)
// -5

上面代碼找出數組中第一個小於 0 的成員。

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

上面代碼中,find方法的回調函數能夠接受三個參數,依次爲當前的值、當前的位置和原數組。

數組實例的findIndex方法的用法與find方法很是相似,返回第一個符合條件的數組成員的位置,若是全部成員都不符合條件,則返回-1

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

5.五、對象的擴展

ES6 容許在大括號裏面,直接寫入變量和函數,做爲對象的屬性和方法。這樣的書寫更加簡潔。

const foo = 'bar';

const baz = {foo};

baz // {foo: "bar"}

// 等同於const baz = {foo: foo};

除了屬性簡寫,方法也能夠簡寫。

const o = {

 	method() {

 	 	return "Hello!";

 	}
};

// 等同於

const o = {

 	method: function() {

  		return "Hello!";

 	}
};

對象的新方法

Object.assign(target, source_1, ···)

用於將源對象的全部可枚舉屬性複製到目標對象中。

基本用法

let target = {a: 1}; 

let object2 = {b: 2}; 

let object3 = {c: 3}; 

Object.assign(target,object2,object3); // 第一個參數是目標對象,後面的參數是源對象 
target; // {a: 1, b: 2, c: 3}

六、Class基本使用和繼承

6.一、類的由來

JavaScript 語言中,生成實例對象的傳統方法是經過構造函數。下面是一個例子。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面這種寫法跟傳統的面嚮對象語言(好比 C++ 和 Java)差別很大,很容易讓新學習這門語言的程序員感到困惑。

ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,做爲對象的模板。經過class關鍵字,能夠定義類。

基本上,ES6 的class能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。上面的代碼用 ES6 的class改寫,就是下面這樣。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

上面代碼定義了一個「類」,能夠看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則表明實例對象。也就是說,ES5 的構造函數Point,對應 ES6 的Point類的構造方法。

Point類除了構造方法,還定義了一個toString方法。注意,定義「類」的方法的時候,前面不須要加上function這個關鍵字,直接把函數定義放進去了就能夠了。另外,方法之間不須要逗號分隔,加了會報錯。

6.二、constructor方法

constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。

class Point {
}

// 等同於
class Point {
  constructor() {}
}

上面代碼中,定義了一個空的類Point,JavaScript 引擎會自動爲它添加一個空的constructor方法。

6.三、類的實例

生成類的實例的寫法,與 ES5 徹底同樣,也是使用new命令。前面說過,若是忘記加上new,像函數那樣調用Class,將會報錯。

class Point {
  // ...
}

// 報錯
var point = Point(2, 3);

// 正確
var point = new Point(2, 3);

6.四、類的繼承

Class 能夠經過extends關鍵字實現繼承,這比 ES5 的經過修改原型鏈實現繼承,要清晰和方便不少。

class Point {
}

class ColorPoint extends Point {
}

super關鍵字

super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。在這兩種狀況下,它的用法徹底不一樣。

第一種狀況,super做爲函數調用時,表明父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。

class A {}

class B extends A {
  constructor() {
    super();
  }
}

上面代碼中,子類B的構造函數之中的super(),表明調用父類的構造函數。這是必須的,不然 JavaScript 引擎會報錯。

第二種狀況,super做爲對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

上面代碼中,子類B當中的super.p(),就是將super看成一個對象使用。這時,super在普通方法之中,指向A.prototype,因此super.p()就至關於A.prototype.p()

6.五、靜態方法

類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。若是在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

上面代碼中,Foo類的classMethod方法前有static關鍵字,代表該方法是一個靜態方法,能夠直接在Foo類上調用(Foo.classMethod()),而不是在Foo類的實例上調用。若是在實例上調用靜態方法,會拋出一個錯誤,表示不存在該方法。

6.六、靜態屬性

靜態屬性指的是 Class 自己的屬性,即Class.propName,而不是定義在實例對象(this)上的屬性。

class Foo {
}

Foo.prop = 1;
Foo.prop // 1

上面的寫法爲Foo類定義了一個靜態屬性prop

目前,只有這種寫法可行,由於 ES6 明確規定,Class 內部只有靜態方法,沒有靜態屬性。如今有一個提案提供了類的靜態屬性,寫法是在實例屬性的前面,加上static關鍵字。

class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myStaticProp); // 42
  }
}

這個新寫法大大方便了靜態屬性的表達。

// 老寫法
class Foo {
  // ...
}
Foo.prop = 1;

// 新寫法
class Foo {
  static prop = 1;
}

上面代碼中,老寫法的靜態屬性定義在類的外部。整個類生成之後,再生成靜態屬性。這樣讓人很容易忽略這個靜態屬性,也不符合相關代碼應該放在一塊兒的代碼組織原則。另外,新寫法是顯式聲明(declarative),而不是賦值處理,語義更好。

七、Set和Map數據結構

7.一、Set

ES6 提供了新的數據結構 Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。

基礎用法:

let mySet = new Set();
 
mySet.add(1); // Set(1) {1}
mySet.add(5); // Set(2) {1, 5}
mySet.add(5); // Set(2) {1, 5} 這裏體現了值的惟一性
mySet.add("some text"); 
// Set(3) {1, 5, "some text"} 這裏體現了類型的多樣性
var o = {a: 1, b: 2}; 
mySet.add(o);
mySet.add({a: 1, b: 2}); 
// Set(5) {1, 5, "some text", {…}, {…}} 
// 這裏體現了對象之間引用不一樣不恆等,即便值相同,Set 也能存儲

上面代碼經過add()方法向 Set 結構加入成員,結果代表 Set 結構不會添加劇復的值。

Set函數能夠接受一個數組(或者具備 iterable 接口的其餘數據結構)做爲參數,用來初始化。

// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

數據類型轉換

Array與Set類型轉換

// Array 轉 Set
var mySet = new Set(["value1", "value2", "value3"]);
// 用...操做符,將 Set 轉 Array
var myArray = [...mySet];

//Array.from方法能夠將 Set 結構轉爲數組。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

String與Set類型轉換

// String 轉 Set
var mySet = new Set('hello');  // Set(4) {"h", "e", "l", "o"}
// 注:Set 中 toString 方法是不能將 Set 轉換成 String

Set實例的屬性

  • Set.prototype.constructor:構造函數,默認就是Set函數。
  • Set.prototype.size:返回Set實例的成員總數。

Set實例的操做方法

  • Set.prototype.add(value):添加某個值,返回 Set 結構自己。
  • Set.prototype.delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
  • Set.prototype.has(value):返回一個布爾值,表示該值是否爲Set的成員。
  • Set.prototype.clear():清除全部成員,沒有返回值。

代碼示例:

s.add(1).add(2).add(2);
// 注意2被加入了兩次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false

Set實例的遍歷方法

  • Set.prototype.keys():返回鍵名的遍歷器
  • Set.prototype.values():返回鍵值的遍歷器
  • Set.prototype.entries():返回鍵值對的遍歷器
  • Set.prototype.forEach():使用回調函數遍歷每一個成員

須要特別指出的是,Set的遍歷順序就是插入順序。這個特性有時很是有用,好比使用 Set 保存一個回調函數列表,調用時就能保證按照添加順序調用。

代碼示例:

keys方法、values方法、entries方法返回的都是遍歷器對象(詳見《Iterator 對象》一章)。因爲 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致。

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

forEach()代碼示例:

let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

遍歷的應用

(1)數組去重

var mySet = new Set([1, 2, 3, 4, 4]);
[...mySet]; // [1, 2, 3, 4]

(2)並集

var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var union = new Set([...a, ...b]); // {1, 2, 3, 4}

(3)交集

var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}

(4)差集

var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var difference = new Set([...a].filter(x => !b.has(x))); // {1}

7.二、Map

Map 對象保存鍵值對。任何值(對象或者原始值) 均可以做爲一個鍵或一個值。

基本用法:

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

Map中的key

key是字符串

var myMap = new Map();
var keyString = "a string"; 
 
myMap.set(keyString, "和鍵'a string'關聯的值");
 
myMap.get(keyString);    // "和鍵'a string'關聯的值"
myMap.get("a string");   // "和鍵'a string'關聯的值"
                         // 由於 keyString === 'a string'

key是對象

var myMap = new Map();
var keyObj = {}, 
 
myMap.set(keyObj, "和鍵 keyObj 關聯的值");

myMap.get(keyObj); // "和鍵 keyObj 關聯的值"
myMap.get({}); // undefined, 由於 keyObj !== {}

key是函數

var myMap = new Map();
var keyFunc = function () {}, // 函數
 
myMap.set(keyFunc, "和鍵 keyFunc 關聯的值");
 
myMap.get(keyFunc); // "和鍵 keyFunc 關聯的值"
myMap.get(function() {}) // undefined, 由於 keyFunc !== function () {}

Map的遍歷

對 Map 進行遍歷,如下兩個最高級。

(1)for…of

var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
 
// 將會顯示兩個 log。 一個是 "0 = zero" 另外一個是 "1 = one"
for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}
for (var [key, value] of myMap.entries()) {
  console.log(key + " = " + value);
}
/* 這個 entries 方法返回一個新的 Iterator 對象,它按插入順序包含了 Map 對象中每一個元素的 [key, value] 數組。 */
 
// 將會顯示兩個log。 一個是 "0" 另外一個是 "1"
for (var key of myMap.keys()) {
  console.log(key);
}
/* 這個 keys 方法返回一個新的 Iterator 對象, 它按插入順序包含了 Map 對象中每一個元素的鍵。 */
 
// 將會顯示兩個log。 一個是 "zero" 另外一個是 "one"
for (var value of myMap.values()) {
  console.log(value);
}
/* 這個 values 方法返回一個新的 Iterator 對象,它按插入順序包含了 Map 對象中每一個元素的值。 */

(2)forEach()

var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
 
// 將會顯示兩個 logs。 一個是 "0 = zero" 另外一個是 "1 = one"
myMap.forEach(function(value, key) {
  console.log(key + " = " + value);
}, myMap)

八、Promise的對象

8.一、Promise概述

是異步編程的一種解決方案。

從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。

基本用法:

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Promise構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolvereject。它們是兩個函數,由 JavaScript 引擎提供,不用本身部署。

resolve函數的做用是,將Promise對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;reject函數的做用是,將Promise對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。

Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數。

then方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise對象的狀態變爲resolved時調用,第二個回調函數是Promise對象的狀態變爲rejected時調用。其中,第二個函數是可選的,不必定要提供。這兩個函數都接受Promise對象傳出的值做爲參數。

8.二、Promise狀態的特色

Promise 異步操做有三種狀態:pending(進行中)、fulfilled(已成功)和 rejected(已失敗)。除了異步操做的結果,任何其餘操做都沒法改變這個狀態。

Promise 對象只有:從 pending 變爲 fulfilled 和從 pending 變爲 rejected 的狀態改變。只要處於 fulfilled 和 rejected ,狀態就不會再變了即 resolved(已定型)。

const p1 = new Promise(function(resolve,reject){
    resolve('success1');
    resolve('success2');
}); 
const p2 = new Promise(function(resolve,reject){  
    resolve('success3'); 
    reject('reject');
});
p1.then(function(value){  
    console.log(value); // success1
});
p2.then(function(value){ 
    console.log(value); // success3
});

狀態的缺點

沒法取消 Promise ,一旦新建它就會當即執行,沒法中途取消。

若是不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。

當處於 pending 狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

8.三、Promise的方法

then()方法

then 方法接收兩個函數做爲參數,第一個參數是 Promise 執行成功時的回調,第二個參數是 Promise 執行失敗時的回調,兩個函數只會有一個被調用。

在 JavaScript 事件隊列的當前運行完成以前,回調函數永遠不會被調用。

const p = new Promise(function(resolve,reject){
  resolve('success');
});
 
p.then(function(value){
  console.log(value);
});
 
console.log('first');
// first
// success

catch()方法

Promise.prototype.catch方法是.then(null, rejection).then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理 getJSON 和 前一個回調函數運行時發生的錯誤
  console.log('發生錯誤!', error);
});

上面代碼中,getJSON方法返回一個 Promise 對象,若是該對象狀態變爲resolved,則會調用then方法指定的回調函數;若是異步操做拋出錯誤,狀態就會變爲rejected,就會調用catch方法指定的回調函數,處理這個錯誤。另外,then方法指定的回調函數,若是運行中拋出錯誤,也會被catch方法捕獲。

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同於
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

all()方法

Promise.all()方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.all([p1, p2, p3]);

上面代碼中,Promise.all()方法接受一個數組做爲參數,p1p2p3都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。另外,Promise.all()方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。

p的狀態由p1p2p3決定,分紅兩種狀況。

(1)只有p1p2p3的狀態都變成fulfilledp的狀態纔會變成fulfilled,此時p1p2p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1p2p3之中有一個被rejectedp的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

下面是一個具體的例子。

// 生成一個Promise對象的數組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});

上面代碼中,promises是包含 6 個 Promise 實例的數組,只有這 6 個實例的狀態都變成fulfilled,或者其中有一個變爲rejected,纔會調用Promise.all方法後面的回調函數。

九、async函數

9.一、概念

ES2017 標準引入了 async 函數,使得異步操做變得更加方便。

async 函數是什麼?一句話,它就是 Generator 函數的語法糖

9.二、基本用法

async函數返回一個 Promise 對象,可使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。

下面是一個例子。

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

上面代碼是一個獲取股票報價的函數,函數前面的async關鍵字,代表該函數內部有異步操做。調用該函數時,會當即返回一個Promise對象。

9.三、語法

async函數返回一個 Promise 對象。

async函數內部return語句返回的值,會成爲then方法回調函數的參數。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

上面代碼中,函數f內部return命令返回的值,會被then方法回調函數接收到。

async函數內部拋出錯誤,會致使返回的 Promise 對象變爲reject狀態。拋出的錯誤對象會被catch方法回調函數接收到。

async function f() {
  throw new Error('出錯了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出錯了
相關文章
相關標籤/搜索