ECMAScript 6.0(如下簡稱 ES6)是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式發佈了。它的目標,是使得 JavaScript 語言能夠用來編寫複雜的大型應用程序,成爲企業級開發語言。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)。平常場合,這兩個詞是能夠互換的。前端
這個問題能夠轉換一種問法,就是學完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語法轉換爲ES5語法。node
先建立一個項目,項目中有兩個文件夾,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>
第一步: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
ES2015(ES6) 新增長了兩個重要的 JavaScript 關鍵字: let 和 const。
let 聲明的變量只在 let 命令所在的代碼塊內有效,const 聲明一個只讀的常量,一旦聲明,常量的值就不能改變。
let命令有如下特色:
(1)代碼塊內有效
ES2015(ES6) 新增長了兩個重要的 JavaScript 關鍵字: let 和 const。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 是很常見的,如今有了這種規定,避免此類錯誤就很容易了。
總之,暫時性死區的本質就是,只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。
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 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(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
面代碼表示,能夠從數組中提取值,按照對應位置,對變量賦值。
本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。
基本用法
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尚未聲明。
(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"}]
下面是另外一個例子。
(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。方括號這種寫法,屬於「屬性名錶達式」。
變量的解構賦值用途不少。
(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");
傳統的 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>
(1)子串的識別
ES6 以前判斷字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的識別方法。
以上三個方法均可以接受兩個參數,須要搜索的字符串,和可選的搜索起始位置索引。
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
個位置直到字符串結束。
注意點:
(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 之間的小數,取整之後等於 -0
, repeat
視同爲 0。
'na'.repeat(-0.9) // ""
參數 NaN
等同於 0。
'na'.repeat(NaN) // ""
若是 repeat
的參數是字符串,則會先轉換成數字。
'na'.repeat('na') // "" 'na'.repeat('3') // "nanana"
(3)字符串補全
ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。
以上兩個方法接受兩個參數,第一個參數是指定生成的字符串的最小長度,第二個參數是用來補全的字符串。若是沒有指定第二個參數,默認用空格填充。
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()
的別名。
(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 的寫法還有兩個好處:首先,閱讀代碼的人,能夠馬上意識到哪些參數是能夠省略的,不用查看函數體或文檔;其次,有利於未來的代碼優化,即便將來的版本在對外接口中,完全拿掉這個參數,也不會致使之前的代碼沒法運行。
參數變量是默認聲明的,因此不能用let
或const
再次聲明。
function foo(x = 5) { let x = 1; // error const x = 2; // error }
上面代碼中,參數變量x
是默認聲明的,在函數體中,不能用let
或const
再次聲明,不然會報錯。
使用參數默認值時,函數不能有同名參數。
// 不報錯 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 命令,不然會報錯
(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
上面代碼中,a3
和a4
是用兩種不一樣方法合併而成的新數組,可是它們的成員都是對原數組成員的引用,這就是淺拷貝。若是修改了原數組的成員,會同步反映到新數組。
(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
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}
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
這個關鍵字,直接把函數定義放進去了就能夠了。另外,方法之間不須要逗號分隔,加了會報錯。
constructor
方法是類的默認方法,經過new
命令生成對象實例時,自動調用該方法。一個類必須有constructor
方法,若是沒有顯式定義,一個空的constructor
方法會被默認添加。
class Point { } // 等同於 class Point { constructor() {} }
上面代碼中,定義了一個空的類Point
,JavaScript 引擎會自動爲它添加一個空的constructor
方法。
生成類的實例的寫法,與 ES5 徹底同樣,也是使用new
命令。前面說過,若是忘記加上new
,像函數那樣調用Class
,將會報錯。
class Point { // ... } // 報錯 var point = Point(2, 3); // 正確 var point = new Point(2, 3);
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()
。
類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。若是在一個方法前,加上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
類的實例上調用。若是在實例上調用靜態方法,會拋出一個錯誤,表示不存在該方法。
靜態屬性指的是 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),而不是賦值處理,語義更好。
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}
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 是一個對象,從它能夠獲取異步操做的消息。
基本用法:
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操做成功 */){ resolve(value); } else { reject(error); } }); promise.then(function(value) { // success }, function(error) { // failure });
Promise
構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve
和reject
。它們是兩個函數,由 JavaScript 引擎提供,不用本身部署。
resolve
函數的做用是,將Promise
對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;reject
函數的做用是,將Promise
對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。
Promise
實例生成之後,能夠用then
方法分別指定resolved
狀態和rejected
狀態的回調函數。
then
方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise
對象的狀態變爲resolved
時調用,第二個回調函數是Promise
對象的狀態變爲rejected
時調用。其中,第二個函數是可選的,不必定要提供。這兩個函數都接受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 狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。
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()
方法接受一個數組做爲參數,p1
、p2
、p3
都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。另外,Promise.all()
方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。
p
的狀態由p1
、p2
、p3
決定,分紅兩種狀況。
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成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
方法後面的回調函數。
ES2017 標準引入了 async 函數,使得異步操做變得更加方便。
async 函數是什麼?一句話,它就是 Generator 函數的語法糖
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
對象。
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: 出錯了