快速掌握es6+新特性及es6核心語法盤點

首先先祝各位國慶快樂,好好去體驗生活的快樂,也祝祖國生日快樂,越變越強大,愈來愈繁榮。 javascript

接下來我會總結一些工做中經常使用也比較核心的es6+的語法知識,後面又要慢慢開始工做之旅了,但願在總結本身經驗的過程當中你們會有所收穫~

正文

1. let和const

let

用法相似於var,可是所聲明的變量,只在let命令所在的代碼塊內有效,即let聲明的是一個塊做用域內的變量。css

特色:html

  • 不存在變量提高
  • 暫時性死區——只要塊級做用域內存在let命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響
  • 不容許重複聲明
  • 塊級做用域——被{}包裹的,外部不能訪問內部

應用案例與分析:前端

// 使用var
for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  });
}   // => 5 5 5 5 5

// 使用let
for (let i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  });
}   // => 0 1 2 3 4
複製代碼

上面使用let的代碼中,變量i是let聲明的,當前的i只在本輪循環有效,因此每一次循環的i其實都是一個新的變量,JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算因此最後能正常輸出i的值。vue

注意:java

  1. for循環還有一個特別之處,就是設置循環變量的那部分是一個父做用域,而循環體內部是一個單獨的子做用域,因此咱們能夠在循環體內部訪問到i的值。
  2. let和var全局聲明時,var能夠經過window的屬性訪問而let不能。

const

const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。const實際上保證的是變量指向的那個內存地址所保存的數據不得改動。對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,所以等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的,至於它指向的數據結構是否是可變的,就徹底不能控制了。所以,將一個對象聲明爲常量必須很是當心。node

所以,咱們使用const時,不能只聲明而不初始化值,不然會報錯:react

const a;
// SyntaxError: Missing initializer in const declaration
複製代碼

const的其餘特性和let很像,通常推薦用它來聲明常量,而且常量名大寫。css3

2. 數值的擴展

ES6 在Number對象上,新提供了Number.isFinite()和Number.isNaN()兩個方法。es6

Number.isFinite()

Number.isFinite()用來檢查一個數值是否爲有限的(finite),即不是Infinity。 注意,若是參數類型不是數值,Number.isFinite一概返回false。

Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite('hello');  // false
Number.isFinite(true);  // false
複製代碼

Number.isNaN()

用來檢查一個值是否爲NaN,若是參數類型不是NaN,Number.isNaN一概返回false

Number.isFinite和Number.isNaN與傳統的全局方法isFinite()和isNaN()的區別在於,傳統方法先調用Number()將非數值的值轉爲數值,再進行判斷,而這兩個新方法只對數值有效,Number.isFinite()對於非數值一概返回false, Number.isNaN()只有對於NaN才返回true,非NaN一概返回false。

isFinite(11) // true
isFinite("11") // true
Number.isFinite(11) // true
Number.isFinite("11") // false

isNaN(NaN) // true
isNaN("NaN") // true
Number.isNaN(NaN) // true
Number.isNaN("NaN") // false
Number.isNaN(10) // false
複製代碼

Number.parseInt(), Number.parseFloat()

ES6 將全局方法parseInt()和parseFloat(),移植到Number對象上面,行爲徹底保持不變,這樣作的目的,是逐步減小全局性方法,使得語言逐步模塊化

Number.isInteger()

Number.isInteger()用來判斷一個數值是否爲整數;JavaScript 內部,整數和浮點數採用的是一樣的儲存方法,因此 25 和 25.0 被視爲同一個值;若是參數不是數值,Number.isInteger返回false;因爲 JavaScript 數值存儲爲64位雙精度格式,數值精度最多能夠達到 53 個二進制位,若是數值的精度超過這個限度,第54位及後面的位就會被丟棄,這種狀況下,Number.isInteger可能會誤判。

Number.isInteger(15) // true
Number.isInteger(15.1) // false
Number.isInteger(15.0) // true
Number.isInteger('10') // false
Number.isInteger(true) // false
// 超出精度範圍會誤判
Number.isInteger(5.0000000000000002) // true
複製代碼

Math.trunc()

Math.trunc方法用於去除一個數的小數部分,返回整數部分;對於非數值,Math.trunc內部使用Number方法將其先轉爲數值;對於空值和沒法截取整數的值,返回NaN

Math.trunc(2.1) // 2
Math.trunc(-2.9) // -2
Math.trunc(-0.1254) // -0
Math.trunc('125.456') // 125
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
Math.trunc(NaN);      // NaN
Math.trunc('bar');    // NaN
Math.trunc();         // NaN
Math.trunc(undefined) // NaN
複製代碼

Math.cbrt()

Math.cbrt方法用於計算一個數的立方根;對於非數值,Math.cbrt方法內部也是先使用Number方法將其轉爲數值

Math.cbrt(-1) // -1
Math.cbrt(0)  // 0
Math.cbrt(8)  // 2
Math.cbrt('8') // 2
Math.cbrt('hello') // NaN
複製代碼

Math.hypot()

Math.hypot方法返回全部參數的平方和的平方根

Math.hypot(3, 4);        // 5
Math.hypot(3, 4, 5);     // 7.0710678118654755
Math.hypot();            // 0
Math.hypot(NaN);         // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5');   // 7.0710678118654755
Math.hypot(-3); 
複製代碼

有了這個api,咱們算一個n維勾股定理是否是很方便了呢?

指數運算符

ES2016 新增了一個指數運算符(**)。這個運算符的一個特色是右結合,而不是常見的左結合。多個指數運算符連用時,是從最右邊開始計算的。

// 至關於 2 ** (3 ** 2)
2 ** 3 ** 2  // 512
// => Math.pow(2, Math.pow(3,2))
複製代碼

es6+還擴展了更多的數值api,感興趣的能夠本身去學習研究。

3. 數組的擴展

擴展運算符

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

應用:

  1. 複製數組
const a1 = [1, 2];
const a2 = [...a1];
複製代碼
  1. 合併數組
const arr1 = ['1', '2'];
const arr2 = ['c', {a:1} ];

// ES6 的合併數組
[...arr1, ...arr2]
複製代碼

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

  1. 將字符串轉化爲數組

使用擴展運算符可以正確識別四個字節的 Unicode 字符。凡是涉及到操做四個字節的 Unicode 字符的函數,都有這個問題。所以,最好都用擴展運算符改寫。

[...'xuxi']
// [ "x", "u", "x", "i" ]
複製代碼
  1. 實現了 Iterator 接口的對象
let nodeList = document.querySelectorAll('div');
let arr = [...nodeList];
複製代碼

上面代碼中,querySelectorAll方法返回的是一個NodeList對象。它不是數組,而是一個相似數組的對象。擴展運算符能夠將其轉爲真正的數組,緣由就在於NodeList對象實現了 Iterator 。

Array.from()

Array.from方法用於將類對象轉爲真正的數組:相似數組的對象和可遍歷的對象(包括 ES6 新增的數據結構 Set 和 Map)。

實際應用中咱們更多的是將Array.from用於DOM 操做返回的 NodeList 集合,以及函數內部的arguments對象。

// NodeList對象
let nodeList = document.querySelectorAll('p')
let arr = Array.from(nodeList)

// arguments對象
function say() {
  let args = Array.from(arguments);
}
複製代碼

Array.from還能夠接受第二個參數,做用相似於數組的map方法,用來對每一個元素進行處理,將處理後的值放入返回的數組。

Array.from([1, 2, 4], (x) => x + 1)
// [2, 3, 5]
複製代碼

Array.of()

Array.of方法用於將一組值,轉換爲數組。Array.of基本上能夠用來替代Array()或new Array(),而且不存在因爲參數不一樣而致使的重載。它的行爲很是統一。

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(2) // [2]
Array.of(21, 2) // [21, 2]
複製代碼

數組實例的 copyWithin()

數組實例的copyWithin()方法,在當前數組內部,將指定位置的成員複製到其餘位置(會覆蓋原有成員),而後返回當前數組。也就是說,使用這個方法,會修改當前數組。

它接受三個參數:

  • target(必需):從該位置開始替換數據。若是爲負值,表示倒數。
  • start(可選):從該位置開始讀取數據,默認爲 0。若是爲負值,表示從末尾開始計算。
  • end(可選):到該位置前中止讀取數據,默認等於數組長度。若是爲負值,表示從末尾開始計算。
[11, 21, 31, 41, 51].copyWithin(0, 3)  // => [41, 51, 31, 41, 51]
// 將3號位複製到0號位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
複製代碼

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

數組實例的find方法用於找出第一個符合條件的數組成員。它的參數是一個回調函數,全部數組成員依次執行該回調函數,直到找出第一個返回值爲true的成員,而後返回該成員。若是沒有符合條件的成員,則返回undefined。數組實例的findIndex方法的用法與find方法很是相似,返回第一個符合條件的數組成員的位置,若是全部成員都不符合條件,則返回-1。這兩個方法均可以接受第二個參數,用來綁定回調函數的this對象。

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

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

// 第二個參數
function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26
複製代碼

數組實例的 fill()

fill方法使用給定值,填充一個數組。fill方法還能夠接受第二個和第三個參數,用於指定填充的起始位置和結束位置。注意,若是填充的類型爲對象,那麼被賦值的是同一個內存地址的對象,而不是深拷貝對象。

new Array(3).fill(7)
// [7, 7, 7]

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

// 填充引用類型
let arr = new Array(2).fill({name: "xuxi"});
arr[0].name = "xu";
arr
// [{name: "xu"}, {name: "xu"}]

let arr = new Array(2).fill([]);
arr[0].push(1);
arr
// [[1], [1]]
複製代碼

數組實例的 includes()

Array.prototype.includes方法返回一個布爾值,表示某個數組是否包含給定的值。該方法的第二個參數表示搜索的起始位置,默認爲0。若是第二個參數爲負數,則表示倒數的位置,若是這時它大於數組長度(好比第二個參數爲-4,但數組長度爲3),則會重置爲從0開始。

[1, 4, 3].includes(3)     // true
[1, 2, 4].includes(3)     // false
[1, 5, NaN, 6].includes(NaN) // true
複製代碼

數組實例的 flat(),flatMap()

flat()用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數據沒有影響。flat()默認只會「拉平」一層,若是想要「拉平」多層的嵌套數組,能夠將flat()方法的參數寫成一個整數,表示想要拉平的層數,默認爲1。若是無論有多少層嵌套,都要轉成一維數組,能夠用Infinity關鍵字做爲參數。flatMap()方法對原數組的每一個成員執行一個函數,而後對返回值組成的數組執行flat()方法。該方法返回一個新數組,不改變原數組。flatMap()只能展開一層數組。flatMap()方法還能夠有第二個參數,用來綁定遍歷函數裏面的this。

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

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

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

// 若是原數組有空位,flat()方法會跳過空位
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]

// flatMap
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
複製代碼

4. 函數的擴展

函數參數的默認值

function say(name = 'xuxi') {
    alert(name)
}
複製代碼

注意點:

  • 參數變量是默認聲明的,因此不能用let或const再次聲明
  • 使用參數默認值時,函數不能有同名參數
  • 參數默認值不是傳值的,而是每次都從新計算默認值表達式的值。也就是說,參數默認值是惰性求值的
  • 參數若是傳入undefined,將觸發該參數等於默認值,null則沒有這個效果。 關鍵點

函數的 length 屬性

指定了默認值之後,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值後,length屬性將失真;若是設置了默認值的參數不是尾參數,那麼length屬性也再也不計入後面的參數了。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
// 參數不是尾參數
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
複製代碼

做用域

一旦設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域。等到初始化結束,這個做用域就會消失。在不設置參數默認值時,是不會出現的。

箭頭函數

因爲箭頭函數的用法比較簡單,咱們來看看注意點:

  • 函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。

  • 不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。

  • 不可使用arguments對象,該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。

  • 不可使用yield命令,所以箭頭函數不能用做 Generator 函數。

不適合場景:

// 定義對象的方法,且該方法內部包括this,
// 由於對象不構成單獨的做用域,致使say箭頭函數定義時的做用域就是全局做用域
const person = {
  year: 9,
  say: () => {
    this.year--
  }
}

// 須要動態this的時候,也不該使用箭頭函數
// 代碼運行時,點擊按鈕會報錯,由於button的監聽函數是一個箭頭函數,致使裏面的this是全局對象
var btn = document.getElementById('btn');
btn.addEventListener('click', () => {
  this.classList.add('on');
});
複製代碼

5. 對象的擴展

對象的擴展運算符

對象的擴展運算符(...)用於取出參數對象的全部可遍歷屬性,拷貝到當前對象之中;等同於使用Object.assign()方法

let a = {w: 'xu', y: 'xi'}
let b = {name: '12'}
let ab = { ...a, ...b };
// 等同於
let ab = Object.assign({}, a, b);
複製代碼

Object.is()

用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致;不一樣之處只有兩個:一是+0不等於-0,二是NaN等於自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
複製代碼

Object.assign()

用於對象的合併,將源對象的全部可枚舉屬性,複製到目標對象; 若是隻有一個參數,Object.assign會直接返回該參數; 因爲undefined和null沒法轉成對象,因此若是它們做爲參數,就會報錯; 其餘類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。可是,除了字符串會以數組形式,拷貝入目標對象,其餘值都不會產生效果。

// 合併對象
const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

// 非對象和字符串的類型將忽略
const a1 = '123';
const a2 = true;
const a3 = 10;

const obj = Object.assign({}, a1, a2, a3);
console.log(obj); // { "0": "1", "1": "2", "2": "3" }
複製代碼

注意點:

  • Object.assign方法實行的是淺拷貝,而不是深拷貝。也就是說,若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用
  • 對於嵌套的對象,遇到同名屬性,Object.assign的處理方法是替換,而不是添加
  • Object.assign能夠用來處理數組,可是會把數組視爲對象
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
複製代碼
  • Object.assign只能進行值的複製,若是要複製的值是一個取值函數,那麼將求值後再複製
const a = {
  get num() { return 1 }
};
const target = {};

Object.assign(target, a)
// { num: 1 }
複製代碼

應用場景:

  • 爲對象添加屬性和方法
  • 克隆/合併對象
  • 爲屬性指定默認值

Object.keys()

返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷屬性的鍵名

Object.values()

返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷屬性的鍵值。注意:返回數組的成員順序:若是屬性名爲數值的屬性,是按照數值大小,從小到大遍歷的

const obj = { 100: '1', 2: '2', 7: '3' };
Object.values(obj)
// ["2", "3", "1"]
複製代碼

Object.entries()

返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷屬性的鍵值對數組;若是原對象的屬性名是一個 Symbol 值,該屬性會被忽略

const obj = { a: '1', b: 1, [Symbol()]: 123 };
Object.entries(obj)
// [ ["a", "1"], ["b", 1] ]
複製代碼

Object.fromEntries()

Object.fromEntries()方法是Object.entries()的逆操做,用於將一個鍵值對數組轉爲對象

Object.fromEntries([
  ['a', '1'],
  ['b', 2]
])
// { a: "1", b: 2 }
複製代碼

應用場景:

  • 將鍵值對的數據結構還原爲對象,所以特別適合將 Map 結構轉爲對象
  • 配合URLSearchParams對象,將查詢字符串轉爲對象
Object.fromEntries(new URLSearchParams('name=xuxi&year=24'))
// { name: "xuxi", year: "24" }
複製代碼

6. symbol

ES6 引入了一種新的原始數據類型Symbol,表示惟一的值。它是 JavaScript 語言的第七種數據類型,前六種是:undefined、null、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)。Symbol 值經過Symbol函數生成。凡是屬性名屬於 Symbol 類型,就都是獨一無二的,能夠保證不會與其餘屬性名產生衝突。

注意點:

  • Symbol函數前不能使用new命令,不然會報錯
  • 因爲 Symbol 值不是對象,因此不能添加屬性。本質上,它是一種相似於字符串的數據類型
  • Symbol函數能夠接受一個字符串做爲參數,表示對 Symbol 實例的描述,方便區分
  • Symbol函數的參數只是表示對當前 Symbol 值的描述,所以相同參數的Symbol函數的返回值是不相等的
  • Symbol 值不能與其餘類型的值進行運算,會報錯
  • Symbol 值做爲對象屬性名時,不能用點運算符
  • 在對象的內部,使用 Symbol 值定義屬性時,Symbol 值必須放在方括號之中
  • Symbol 值做爲屬性名時,該屬性仍是公開屬性,不是私有屬性
  • Symbol 做爲屬性名時屬性不會出如今for...in、for...of循環中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。可是,它也不是私有屬性,使用Object.getOwnPropertySymbols方法能夠獲取指定對象的全部 Symbol 屬性名。

Symbol.for(),Symbol.keyFor()

Symbol.for()接受一個字符串做爲參數,而後搜索有沒有以該參數做爲名稱的 Symbol 值。若是有,就返回這個 Symbol 值,不然就新建並返回一個以該字符串爲名稱的 Symbol 值。Symbol.keyFor方法返回一個已登記的 Symbol 類型值的key

let a1 = Symbol.for('123');
let a2 = Symbol.for('123');

a1 === a2 // true

// Symbol.for()與Symbol()這兩種寫法,都會生成新的Symbol。
// 它們的區別是,前者會被登記在全局環境中供搜索,後者不會
Symbol.keyFor(a1) // "123"
let c2 = Symbol("f");
Symbol.keyFor(c2) // undefined
複製代碼

7. set和map數據結構

set

ES6提供了新的數據結構Set,相似於數組,可是成員的值都是惟一的,沒有重複的值。Set自己是一個構造函數,用來生成Set數據結構。

實例屬性和方法:

  • add(value):添加某個值,返回Set結構自己。
  • delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
  • has(value):返回一個布爾值,表示該值是否爲Set的成員。
  • clear():清除全部成員,沒有返回值。
s.add(1).add(3).add(3);
// 注意3被加入了兩次

s.size // 2

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

s.delete(3);
s.has(3) // false
複製代碼

遍歷操做:

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

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

應用場景:

// 數組去重
let arr = [1223];
let unique = [...new Set(arr)];
// or
function dedupe(array) {
  return Array.from(new Set(array));
}

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

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

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
複製代碼

map

相似於對象,也是鍵值對的集合,各類類型的值(包括對象)均可以看成鍵。Map結構提供了「值—值」的對應,是一種更完善的Hash結構實現。

實例屬性和方法:

  • size屬性: 返回Map結構的成員總數
  • set(key, value): set方法設置key所對應的鍵值,而後返回整個Map結構。若是key已經有值,則鍵值會被更新,不然就新生成該鍵,set方法返回的是Map自己,所以能夠採用鏈式寫法
  • get(key) : get方法讀取key對應的鍵值,若是找不到key,返回undefined
  • has(key) : has方法返回一個布爾值,表示某個鍵是否在Map數據結構中
  • delete(key) : delete方法刪除某個鍵,返回true。若是刪除失敗,返回false
  • clear() : clear方法清除全部成員,沒有返回值

遍歷方法和set相似,Map結構轉爲數組結構,比較快速的方法是結合使用擴展運算符(...):

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
複製代碼

數組轉map:

new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}
複製代碼

Map轉爲對象:

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
複製代碼

對象轉爲Map

function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

objToStrMap({yes: true, no: false})
// [ [ 'yes', true ], [ 'no', false ] ]
複製代碼

8. Proxy 和 Reflect

關於Proxy 和 Reflect的介紹,我會單獨寫個完整的模塊來介紹及其應用。

9. promise對象

Promise是異步編程的一種解決方案,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件的結果。從語法上說,Promise是一個對象,從它能夠獲取異步操做的消息。

特色:

  • 對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:Pending(進行中)、Resolved(已完成,又稱Fulfilled)和Rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
  • 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。整個過程不可逆。

使用:

  • 基本用法
// 實現異步加載圖片
function loadImageAsync(url) {
 return new Promise(function(resolve, reject) {
   var image = new Image();

   image.onload = function() {
     resolve(image);
   };

   image.onerror = function() {
     reject(new Error('圖片加載失敗'));
   };

   image.src = url;
 });
}
// 使用
loadImageAsync('http://xxxx/api').then((data) => {
   // some code
}).catch(err => console.log(err))
複製代碼

resolve函數將Promise對象的狀態從「未完成」變爲「成功」,即Pending => Resolved,並將異步操做的結果做爲參數傳遞出去;reject函數將Promise對象的狀態從「未完成」變爲「失敗」,即Pending => Rejected,並將異步操做報出的錯誤做爲參數傳遞出去。

Promise實例生成之後,能夠用then方法分別指定Resolved狀態和Reject狀態的回調函數。then方法的第一個參數是Resolved狀態的回調函數,第二個參數(可選)是Rejected狀態的回調函數。

then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

  • Promise.all()

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。只有Promise.all內的全部promise狀態都變成fulfilled,它的狀態纔會變成fulfilled,此時內部promise的返回值組成一個數組,傳遞給Promise.all的回調函數。只要Promise.all內部有一個promise被rejected,Promise.all的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

  • Promise.race()

Promise.race方法一樣是將多個Promise實例,包裝成一個新的Promise實例。只要Promise.race中有一個實例率先改變狀態,Promise.race的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給Promise.race的回調函數。

實現請求超時處理:

const ajaxWithTime = (url, ms) => Promise.race([
  fetch(url),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), ms)
  })
])
ajaxWithTime('http://xxx', 5000).then(response => console.log(response))
.catch(error => console.log(error))
複製代碼
  • Promise.resolve()

將現有對象轉爲Promise對象。若是參數是Promise實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例;若是參數是一個原始值,或者是一個不具備then方法的對象,則Promise.resolve方法返回一個新的Promise對象,狀態爲Resolved;須要注意的是,當即resolve的Promise對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時。

setTimeout(function () {
  console.log('3');
}, 0);

Promise.resolve().then(function () {
  console.log('2');
});

console.log('1');

// 1
// 2
// 3
複製代碼
  • finally()

finally方法用於指定無論Promise對象最後狀態如何,都會執行的操做。它接受一個普通的回調函數做爲參數,該函數無論怎樣都必須執行

10. async函數

async函數就是Generator函數的語法糖,async函數的await命令後面,能夠是Promise對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。async函數的返回值是Promise對象,你能夠用then方法指定下一步的操做。進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個Promise對象,而await命令就是內部then命令的語法糖。

應用案例:

1.指定時間後返回數據

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('xu xi', 5000);
複製代碼

注意事項:

  • await命令後面的Promise對象,運行結果多是rejected,因此最好把await命令放在try...catch代碼塊中
  • 多個await命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發
let [a, b] = await Promise.all([a(), b()]);
複製代碼
  • await命令只能用在async函數之中,若是用在普通函數,就會報錯

應用場景:

  1. 按順序完成異步操做
async function fetchInOrder(urls) {
  // 併發讀取遠程請求
  const promises = urls.map(async url => {
    const res = await fetch(url);
    return res.text();
  });

  // 按次序輸出
  for (const promise of promises) {
    console.log(await promise);
  }
}
複製代碼

11. class

經過class關鍵字,能夠定義類。ES6的class能夠看做只是一個語法糖,它的絕大部分功能,ES5均可以作到,class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。類的數據類型就是function,類自己就指向構造函數。構造函數的prototype屬性,在ES6的「類」上面繼續存在。類的全部方法都定義在類的prototype屬性上面。另外,類的內部全部定義的方法,都是不可枚舉的.

class Person {
  constructor(){
    // ...
  }

  toString(){
    // ...
  }
}

// 等同於

Person.prototype = {
  toString(){},
  toValue(){}
};

// 不可枚舉
Object.keys(Person.prototype)
// []
Object.getOwnPropertyNames(Person.prototype)
// ["constructor","toString"]
// 注:ES5的寫法,toString方法是可枚舉的
複製代碼

constructor方法

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

注:類的構造函數,不使用new是無法調用的,會報錯。這是它跟普通構造函數的一個主要區別,後者不用new也能夠執行。

不存在變量提高

this的指向

類的方法內部若是含有this,它默認指向類的實例。可是,必須很是當心,一旦單獨使用該方法,極可能報錯

// 解決this指向問題
class Say {
  constructor() {
    // 在構造方法中綁定this或者使用箭頭函數
    this.sayName = this.sayName.bind(this);
  }
}
複製代碼

Class的繼承

Class之間能夠經過extends關鍵字實現繼承,子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,若是不調用super方法,子類就得不到this對象。

class Color extends Point {
  constructor(x, y, name) {
    super(x, y); // 調用父類的constructor(x, y)
    this.name = name;
  }

  toString() {
    return this.name + ' ' + super.toString(); // 調用父類的toString()
  }
}
複製代碼

Class的取值函數(getter)和存值函數(setter)

與ES5同樣,在Class內部可使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲

class MyHTMLElement {
  constructor(element) {
    this.element = element;
  }

  get html() {
    return this.element.innerHTML;
  }

  set html(value) {
    console.log('success', value)
    this.element.innerHTML = value;
  }
}

const el = new MyHTMLElement(el);
el.html('1111')  // success 1111
複製代碼

Class的靜態方法

若是在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。

class Hi {
  static say() {
    return 'hello';
  }
}

Hi.say() // 'hello'

let hi = new Hi();
hi.say()
// TypeError: foo.classMethod is not a function
複製代碼

12. 修飾器Decorator

類的修飾

修飾器是一個用來修改類的行爲的函數。其對類的行爲的改變,是代碼編譯時發生的,而不是在運行時。

function get(target) {
  target.get = 'GET';
}

@get
class MyHttpClass {}

console.log(MyHttpClass.get) // GET

// 若是以爲一個參數不夠用,能夠在修飾器外面再封裝一層函數
function get(type) {
  return function(target) {
    target.type = type;
    // 添加實例屬性
    target.prototype.isDev = true;
  }
}

@get('json')
class MyHttpClass {}
MyHttpClass.type // 'json'

let http = new MyHttpClass();
http.isDev // true
複製代碼

方法的修飾

修飾器不只能夠修飾類,還能夠修飾類的屬性,修飾器只能用於類和類的方法,不能用於函數,由於存在函數提高。若是同一個方法有多個修飾器,會像剝洋蔥同樣,先從外到內進入,而後由內向外執行。

修飾類的屬性時,修飾器函數一共能夠接受三個參數,第一個參數是所要修飾的目標對象,第二個參數是所要修飾的屬性名,第三個參數是該屬性的描述對象。

// 下面的@log修飾器,能夠起到輸出日誌的做用
class Kinds {
  list = []
  @log
  add(name) {
    return this.list.push(name)
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value;
  console.log('old', oldValue);
  // ...
  return descriptor;
}

const dog = new Kinds();

dog.add('dog');

// 多個裝飾器的洋蔥式執行順序
function printN(id){
    console.log('print-0', id);
    return (target, property, descriptor) => console.log('print-1', id);
}

class A {
    @printN(1)
    @printN(2)
    say(){}
}
// print-0 1
// print-0 2
// print-1 2
// print-1 1
複製代碼

13. module

模塊功能主要由兩個命令構成:export和import。export命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能。

export

若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量

// lib.js
// 直接導出
export var name = 'xu xi';

// 優先考慮下面這種寫法,更清晰優雅
var year = 1958;
export {year};

//導出函數
export function multiply(x, y) {
  return x * y;
};

// 使用as關鍵字重命名
export {
  year as y
};

// 注意:export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係
var a = 1;
// 報錯,由於經過變量a,直接輸出的是1,1只是一個值,不是接口
export a;
// 同理,下面的也會報錯
function f() {}
export f;

// 另外,export語句輸出的接口,與其對應的值是動態綁定關係,
// 即經過該接口,能夠取到模塊內部實時的值
export var a = 1;
setTimeout(() => a = 2, 1000);  // 1s以後a變爲2
複製代碼

export命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,import也是如此。這是由於處於條件代碼塊之中,就無法作靜態優化了,違背了ES6模塊的設計初衷。

import

使用export命令定義了模塊的對外接口後,其餘 JS 文件就能夠經過import命令加載這個模塊。import命令具備提高效果,會提高到整個模塊的頭部,首先執行。是由於import命令是編譯階段執行的,在代碼運行以前。若是屢次重複執行同一句import語句,那麼只會執行一次,而不會執行屢次

// main.js
import {other, year} from './lib';
// 將輸入的變量重命名
import { year as y } from './lib';

// 提高
say()
import { say } from './lib';

// 總體加載模塊
import * as lib from './lib';
console.log(lib.year, lib.say())
複製代碼

export default 命令

爲模塊指定默認輸出

// a.js
export default function () {
  console.log('xu xi');
}

// b.js
import a from './a'
複製代碼

最後

因爲工做中90%以上的業務均可以用以上的es6新特性解決,後期會不斷優化完善,並出一個徹底使用es6完成的實戰項目。更多前端問題在公衆號《趣談前端》加入咱們一塊兒討論吧!

更多推薦

相關文章
相關標籤/搜索