ES6+ 特性整理

ES6新特性

let關鍵字

控制作用域在塊級作用域下的定義關鍵字,并且不允許重復聲明,舉例:

for(var i = 0; i < 10; i++) {}
console.log(i);
// 輸出10
for(let j = 0; j < 10; j++) {}
console.log(j);
// j未定義,報錯(let定義的變量出了其所在代碼塊就銷毀)

注:
在沒有let關鍵字之前,只用用var定義變量,但由于其作用域在函數級別,因此存在很多問題,例如下面希望實現多個按鈕,點擊輸出對應的索引:

<body>
    <button>a</button>
    <button>b</button>
    <button>c</button>
</body>
<script>
btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
    // 使用var定義
    btns[i].onclick = function() {
        console.log(i);
    }
}
</script>

結果發現不管點擊哪個按鈕,輸出的都是3,原因就是var定義的變量i作用域在函數,因此這里的i對于3個按鈕來說是共用的,而這個i經過循環變成了3,所以無論哪個按鈕,獲得到的i都是3。為了解決上述問題,在ES6前提出了閉包的解決方案:

<body>
    <button>a</button>
    <button>b</button>
    <button>c</button>
</body>
<script>
    btns = document.getElementsByTagName('button');
    for (var i = 0; i < btns.length; i++) {
        // 通過閉包,即把i傳入到一個函數內部,避免變量被覆蓋
        (function(i) {
            btns[i].onclick = function() {
                console.log(i);
            }
        })(i);
    }
</script>

但是這樣需要多定義一個函數,代碼上也顯得冗余,因此ES6以后塊級作用域的let關鍵字完美的解決了這個問題:

<body>
    <button>a</button>
    <button>b</button>
    <button>c</button>
</body>
<script>
btns = document.getElementsByTagName('button');
for (let i = 0; i < btns.length; i++) {
    // 使用let定義塊級作用域變量,此時相當于在3個代碼塊里分別let i = 0/1/2,三個i都在自己的作用域下,互相不會沖突
    btns[i].onclick = function() {
        console.log(i);
    }
}
</script>

注2:
關于let是否存在變量提升問題可以參考(事實上是存在的):https://blog.csdn.net/weixin_40169665/article/details/83819825

const關鍵字

定義常量,被定義的常量無法被更改,但是要注意定義常量的時候必須為其初始化,以及其作用域和let一樣只在自己的代碼塊內起作用,并且也不可重復聲明。實際上const相當于一個不能被更改指向的指針,但是如果指向的是對象這樣的數據,那么對象的內容是允許更改的,比如下面的操作是允許的:

const a = {name:1}
// 常量a指向一個對象
a["name"] = 2
// 修改對象的值允許
// const a = {name:1}
// 常量重新指向一個對象,此時不允許
數組解構賦值

允許將數組的值分別賦值給多個變量,舉例:

[x, y] = [1, 2]
// 此時x=1,y=2
[x, y] = [1, 2, 3, 4]
// 此時只有1和2被賦值給x和y,和上面的等價
[x, y, z] = [1, 2]
// z沒有被賦值,為undefined

對于數組當中不希望賦值的數據,可以用逗號間空開,舉例:

[a, , c] = [1, 2, 3]
// 此時a=1,c=3,而2沒有賦值給任何一個數

如果希望把數組剩余的部分都賦值給某個變量,可以用...變量名實現,舉例:

[a, ...c] = [1, 2, 3]
// 此時a=1,c=[2, 3],要注意...開頭的變量必須放在最后

如果擔心數組的內容不夠賦值給變量,還可以給變量設置默認值,舉例:

[a, b, c, d] = [1, 2, 3]
// 此時d沒有被賦值,結果為undefined
[a, b, c, d=4] = [1, 2, 3]
// d設置了默認值為4

對于字符串也可以當做數組來解構,舉例:

[a, b, c] = 'xyz'
// a='x',...

這種解構在函數傳參時也很實用,舉例:

function test([a, b, ...c]){
    console.log(a + b, c);
}
test([1,2,3,4,5])
// 3, [3, 4, 5]
對象解構賦值

對象的解構賦值基于屬性名,舉例:

{a, b, c} = {a:1, b:2}
// a為1,b為2,c沒有被賦值,為undefined

如果希望屬性賦值給變量名不同的變量,則看下面示例:

{a:A, b} = {a:1, b:2}
// 此時a的屬性賦值給變量A,所以變量a為未定義,A為1,b為2

由于對象解構時是使用{}包起來的,因此會被解析成代碼塊,此時就可能出現作用域沖突問題,可以在外面包一層括號解決,舉例:

let x = 0;
({x} = {x:1})

對于多級對象的解構示例如下:

{people: [{name}, age]} = {people:[{name:"aaa"}, 18]}
// 此時name為aaa,age為18

對象解構的默認值設置和數組的同理,舉例:

{x=0,y=0} = {x:1}
// 此時x為1,y為0

還有些用法如提取對象中的方法成變量調用,舉例:

let {floor} = Math;
// 將Math下的floor方法提取成變量
floor(1.5)
// 調用floor方法
let {length:s_length} = 'hello';
// 將字符串長度屬性提取出來
s_length
// 輸出字符串長度

對象解構同樣可以用于函數傳參,且更方便,避免了傳遞順序等問題,舉例:

function test({a, b}){
    console.log(a + b);
}
test({a:1, b:2})

注:
這里順便介紹一些對象的基本用法,比如直接定義函數,舉例:

x = {a:1, b(){return 2;}, c(){console.log(3);}}
// {a: 1, b: ?, c: ?}
x.b()
// 2
x.c()
// 3

直接傳入已賦值的變量或常量,舉例:

let x = 1
const y = 2
z = {x, y}
// 將變量或常量傳入對象,此時z為:{x: 1, y: 2}
z.y = 3
// 但傳入以后都會變成變量,所以z里的y可以修改
z
// {x: 2, y: 3}

設置默認值,舉例:

function test({
    x = 100,
    y = 200
}) {
    console.log(x, y);
}

test({x: 500, y: 200});
// 500 200
數組展開

通過...可以將數組展開傳入,舉例:

function test(a, b, c) {
    console.log(a + b + c);
}
test(...[1, 2, 3])
// 將數組展開成1, 2, 3賦值給a, b, c

數組展開還可以用于數組拼接,舉例:

a = [1,2,3]
b = [4,5,6]
[...a, ...b]
// [1, 2, 3, 4, 5, 6]

實際上數組展開就是把數組當中的一個個數據通過類似迭代器那樣給分離出來,因此需要給一個數據類型來存放所有分離出來的數據,舉例:

a = [1,2,3]
...a
// 報錯:VM323:1 Uncaught SyntaxError: Unexpected token ...,因為分離出來的數據沒有東西存放
[...a]
// 通過數組存放a中分離出來的數據,結果為[1, 2, 3]
對象展開

和數組類似,對象也能夠通過...展開,同時也需要數據類型來存放這個展開后的所有數據,舉例:

a = {name: 111, pwd: 222}
{...a, b:1}
// 通過對象{b: 1}存放...a,結果為:{name: 111, pwd: 222, b: 1}
字符串新增方法
includes方法

判斷字符串是否存在于其中,舉例:

"abc".includes('a')
// true
"abc".includes('d')
// false

注:
該方法對數組同樣適用,舉例:

[1,2,3].includes(1)
// true
startsWith方法

判斷字符串是否以其開頭,舉例:

"abc".startsWith('a')
// true
"abc".startsWith('b')
// false
endsWith方法

判斷字符串是否以其結尾

repeat方法

將字符串復制成原來的幾倍,舉例:

"abc".repeat(3)
// "abcabcabc"
數組新增方法
map方法

對數組數據進行統一操作,舉例:

a = [1,2,3,4,5]
a.map(function(item){return item>=3})
// 統一操作判斷每個數據是否大于等于3,[false, false, true, true, true]
// 由于上面的函數符合一個參數,且只有一條return語句,因此可以簡寫如下:
// 簡寫后:a.map(item=>item>=3)
// 這里容易看混掉的是第一個=>是代表箭頭函數,第二個=>代表大于等于運算符

注:
map方法里其實還有第二個參數,為數據索引,舉例:

a = [1,2,3,4,5]
a.map(function(item, index){return [item, index]})
// [[1,0],[2,1],[3,2],[4,3],[5,4]]
reduce方法

對整個數組進行循環某運算,其下有三個參數為:tmp/item/index,分別代表上一次操作返回的結果(第一次默認為數組的第一個數)、下一個傳入的值(即數據的下一個數)、循環的索引,舉例:

a = [1,2,3,4,5]
a.reduce(function(tmp, item, index) {
    console.log(tmp, item);
    return tmp + item;
    // 返回結果為當前數和下一個數的求和
})
// 最后的結果為15
filter方法

根據條件過濾數據,舉例:

a = [1,2,3,4,5]
a.filter(item => item%2==0)
// 返回所有偶數,結果為[2, 4]
forEach方法

遍歷數組,舉例:

a = [1,2,3,4,5]
a.forEach((item, index) => {console.log(item, index)})
// 循環輸出:1 0、2 1、...
some方法

循環判斷數組中是否存在滿足條件的數據,只要有一個滿足就返回true,舉例:

a = [1,2,3,4,5]
a.some(item=>item>=3)
// 有大于等于3的數,返回true
every方法

some方法類似,循環判斷數組中是否全部滿足條件,是則返回true,舉例:

a = [1,2,3,4,5]
a.every(item=>item>=3)
// 有不大于等于3的數,返回false
對象新增方法
Object.keys()

會將對象的所有鍵以數組形式輸出,舉例:

o = {a:1, b:2, c:3}
Object.keys(o)
// ["a", "b", "c"]
Object.values()

會將對象的所有值以數組形式輸出

Object.entries()

會將對象的所有鍵和值以數組形式輸出,數組中的子元素是長為2的數字,分別為鍵和值,舉例:

o = {a:1, b:2, c:3}
Object.entries(o)
// [Array(2), Array(2), Array(2)]
Object.entries(o)[0]
// ["a", 1]
模板字符串

原來的字符串要拼接值都是通過多個+號實現,而通過反引號包起來的模板字符串則可以通過${value}傳值,舉例:

x = 2
`1${x}3`
// 123

模板字符串還可以嵌套,舉例:

`1${x+`${y}`}3`
// 1203
標簽模板

在定義模板字符串時,可以使用標簽模板對其進行一些操作,舉例:

function toLower(strings, ...params) {
    // 傳入第一個參數為模板字符串中的靜態字符,params接收多個傳入的變量
    console.log(strings, params);
    let s = "";
    strings.forEach((item, index) => {
        if (index >= params.length) return;
        s += item + params[index].toLowerCase();
    })
    s += strings.slice(-1);
    return s;
}

let name = "Aaa";
let result = toLower`My name is ${name}.`;
console.log(result);
// [ 'My name is ', '.' ] [ 'Aaa' ]
// My name is aaa.

參考:http://www.lxweimin.com/p/c65b9930dd11

Symbol數據類型

生成一個每次都不同的隨機變量,一般用于防止對象屬性被修改,舉例:

a = Symbol()
b = Symbol()
a == b
// false
c = {}
c[a] = 1
c[b] = 2
c
// {Symbol(): 1, Symbol(): 2},可以看出a的值沒有被b給覆蓋
Set類型

集合,舉例:

s = new Set([1,2,3,3])
// Set(3) {1, 2, 3},可以看出結果為3個
s.size
// 3,相當于length
s.add(4)
// Set(4) {1, 2, 3, 4},添加了4進去
s.delete(5)
// false,沒有5,刪除失敗
s.delete(3)
// true,刪除成功
s.has(2)
// true,存在2
s.clear()
// 清空集合
迭代器

可以惰性加載數據,并且每個數據只能加載一次,不能回退

自定義迭代器實現
function createIterator(arr) {
  let i = 0;
  let done = false;
  return {
    next() {
      if (i === arr.length) done = true;
      return {value: arr[i++], done};
    }
  }
}

let iterator = createIterator([1,2,3]);
while ((item = iterator.next()) && (!item.done)) {
  console.log(item);
}

// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
通過Symbol實現迭代器
let arr = [1, 2, 3];
let createIterator = arr[Symbol.iterator];
// 創建迭代器函數
let iterator = createIterator.call(arr);
// 綁定迭代器數據
while ((item = iterator.next()) && !item.done) {
  console.log(item);
}

// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
生成器

在函數名前加符號*,并且通過yield關鍵字返回值,通過next方法獲取下一個的值,舉例:

function *gen() {
  yield 1
  yield 2
  yield 3
  return 100
}

let g = gen();
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());

// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 100, done: true }
// { value: undefined, done: true }
生成器特性

生成器相當于可以暫停的函數,因此我們可以在函數上一次返回的地方繼續執行,舉例:

// 無限生成的遞增數字
function *gen() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

let g = gen();
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());

// { value: 0, done: false }
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
函數參數默認值

函數支持給參數設置默認值,舉例:

function test(x, y=1){
  return x + y;
}
test(1);
// 2
test(1, 2);
// 3
常量函數命名

可以通過定義一個常量而不用function關鍵字來命令一個函數,舉例:

const FUN = "test";
// 定義一個常量FUN,值為test
funs = {
    [FUN] (x, y) {
        return x + y; 
    } 
}
// 定義以后,可以發現funs為:{test: ?},即定義了一個名為test的函數
funs.test(1, 2)
// 3

不過本人嘗試以后發現貌似不用常量,用字符串也可以這樣定義(常量傳進對象本身也都不是常量了)

箭頭函數

能夠實現對函數的簡寫,舉例:

function(x, y) {
    ...
}
// 上面的原來函數的寫法
(x, y) => { ... }
// 通過箭頭函數可以簡寫并替代上面的寫法

其中使用箭頭函數有以下注意事項:

  • 當參數只有一個時,可以省略(),舉例:
function(data) {
    console.log(data);
}
// 只有一個參數
data => {console.log(data);}
// 省略了括號
  • 如果函數里只有一個return語句,那么可以不用加{},并且可以省略return關鍵字,舉例:
function(x, y) {
    return x + y;
}
// 只有一個return語句
(x, y) => x + y;
// 省略了花括號和return關鍵字

注:
通過箭頭函數還能夠綁定this,使得this一直為當前的環境而不會被變更,舉例:

a = {
    name: "aaa",
    fn: function() {
        console.log(this, this.name + "bbb")
        // 使用function定義的this會綁定到當前對象,因此輸出為當前對象a和"aaabbb"
    },
    fn1: () => {
        console.log(this, this.name + "bbb")
        // 箭頭函數使得this綁定在window對象,因此輸出為window對象和"bbb"
    }
}
a.fn()
a.fn1()
JSON操作
對象轉json

通過JSON對象的stringify方法實現,舉例:

JSON.stringify({a:1, b:2})
// "{"a":1,"b":2}"
json轉對象

通過JSON對象的parse方法實現,舉例:

JSON.parse('{"a":1,"b":2}')
// {a: 1, b: 2}
面向對象

ES6之前JS的面向對象是一個偽面向對象,存在著許多問題,比如用function關鍵字定義類,以至于不知道是函數還是類、添加方法方式限制不嚴,導致代碼編寫可能不統一等等一系列問題。因此在ES6中提供了幾個面向對象的關鍵字統一了JS的面向對象編程,包括:class(定義類)、constructor(構造器)、extends(繼承)、super(父類),static(定義靜態屬性/方法)舉例:

class User {
    constructor(name, pwd) {
        // 構造方法
        this.name = name;
        this.pwd = pwd;
    }
    showName() {
        // 直接輸入方法名定義方法
        console.log(this.name);
    }
    static showRule() {
        // 定義靜態方法,可以從類直接調用而無需實例化
        console.log("this is User rule");
    }
    static rule = "this is rule";
    // 定義靜態屬性,如果報錯可能是瀏覽器版本不支持,建議換成最新chrome嘗試
}
class Vip extends User {
    // 繼承父類
    constructor(name, pwd, level) {
        super(name, pwd);
        // 調用父類構造方法
        this.level = level;
    }
    showPwd() {
        console.log(this.pwd);
    }
    showLevel() {
        console.log(this.level);
    }
}
console.log(User.rule);
User.showRule();
// 直接從類調用靜態方法
let user = new Vip("aaa", "111", "1");
user.showName();
user.showPwd();
user.showLevel();
類表達式
  • 我們可以通過類表達式來定義匿名類,舉例:
const test = class Test {
    constructor() {
        console.log("init test");
    }
}

new test();
  • 類表達式可以省略類名:
const test = class {
    constructor() {
        console.log("init test");
    }
}

new test();
  • 可以通過該方式創建內部類:
class Base {}

function Factory(cls) {
    return class extends Base {
        test() {
            console.log(cls);
        }
    }
}

const cls = Factory("test")
const instance = new cls();
instance.test();
fetch

fetch是一個新提供的網絡請求api,在fetch出來之前,異步請求主要還是用ajaxaxiosaxios相當于結合ajaxpromise后再封裝了一層)。fetch也是基于promise管理(因此可以像promise對象那樣處理),并且是JS原生的內置類,所以可以直接使用,而無需像ajaxaxios那樣還需要先導入一個庫。并且fetch底層不是基于XMLHttpRequest實現的,官方的意思是用了一種更好的方案實現,不過fetch不支持老版本IE,并且fetch和原來的ajaxaxios不一樣,他只要有服務端返回就算訪問成功(別的會根據狀態碼等來判斷),因此只有網絡連接失敗時才算是訪問失敗。

使用

fetch的使用十分簡單,只需要配置第一個參數url,以及第二個可選參數(請求的配置和傳輸的數據對象等),使用舉例:

fetch("http://jsonplaceholder.typicode.com/users").then(res => res.json()).then(json => {console.log(json)})
// 向該網址發送請求,并且第二個參數不傳默認為get請求。
fetch("http://xxx", {
  method: "post",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({'name': 'aaa'})
})
  .then(res => res.json())
  .then(json => {
    console.log(json);
  });
// 指定發送的數據格式和內容等

更多參考:
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
https://blog.csdn.net/qq_36754767/article/details/89645041

模塊化
使用步驟

1.通過export導出成員
2.在另一個文件當中通過import接收成員
3.引用通過webpack編譯后生成的bundle.js文件
舉例:

  • a.js
export let a = { name: "aaa" };
export const b = 111;
// 導出成員a和b
  • b.js
import * as a from './a'
// 導入a.js里的所有成員,注意相對路徑需要加`./`這樣的定位符
console.log(a.a, a.b);
// 輸出導入的成員
  • webpack.config.js
module.exports = {
    mode: 'development',
    entry: './b.js',
    output: {
        filename: 'bundle.js',
        path: __dirname + '/build'
    }
}

導入webpack生成的文件后可以發現輸出了ab的值。
注:
注意相對路徑需要加./這樣的定位符,因為模塊化需要在node環境下使用,如果不加定位符,則默認去依賴模塊當中尋找,而不會去相對路徑下找

默認成員default

在模塊化當中還可以導出默認成員default,則通過導入模塊成功語法時,只導入默認成員default的值,舉例:

  • a.js
export default "aaa";
// 導出默認成員"aaa"
  • b.js
import a from './a'
// 導入默認成員default,并命名為a,相當于下面這個意思
// import default as a from './a'

console.log(a);

這種語法在vue的組件化工程當中很常見

導入成員語法

前面介紹了兩種語法,一種是導入全部:

import * as xxx from './xxx'

還有導入默認默認成員語法:

import xxx from './xxx'

如果希望導入指定的成員也可以:

import {xxx, yyy as zzz} from './xxx'
// 導入./xxx文件里的xxx和yyy成員,并將yyy改名為zzz

還有導入如CSS文件、圖片文件等模塊時(在模塊化開發下一些靜態文件都稱為模塊),因為這些文件里可能不存在成員啥的,所以可以只導入,舉例:

import './xxx'

還有異步引入,使用import方法,舉例:

let x = import('./xxx')
// 當需要用到的時候引入,簡化代碼體積
// 傳回來的是個Promise對象,需要使用await關鍵字等待
模塊化導入路徑問題
  • 'xxx':直接從npm依賴里尋找相關模塊
  • './xxx'/../xxx:從相對路徑當中尋找相關模塊
  • @/xxx:從src目錄下尋找相關模塊
瀏覽器兼容

對于ES6以上的寫法,在一個古老的瀏覽器上是不兼容的,因此需要對這些內容進行編譯

babel.js編譯

需要安裝node環境

安裝
npm install @babel/core @babel/cli @babel/preset-env -D
使用步驟

1.初始化項目環境:npm init -y
2.在安裝好babel后,在項目下添加一個文件.babelrc,添加內容如下:

{
    presets: ["@babel/preset-env"]
}

3.在src目錄下編寫js文件
4.輸入命令:babel src -d dest,即可在dest目錄發現文件被編譯成兼容低版本瀏覽器的js文件了
注:
如果嫌命令麻煩,也可以打開package.json文件,在script屬性下配置自定義命令:"自定義命令": "babel src -d dest",此時只需要輸入命令:npm run 自定義命令即可

ES7新特性

冪操作

通過**可以實現冪運算,舉例:

2**3
// 8

感覺這越發展越來越像java和python的結合體了

ES8新特性

異步操作控制

對于異步操作(如ajax)本身帶來了很大的便利,但是由于其異步的關系,有些依賴性的異步代碼則很可能陷入一種回調地獄,比如下面的代碼:

$.ajax({
    url: "https://xxx",
    success(data) {
        // 當訪問xxx成功時訪問yyy
        $.ajax({
            url: "https://yyy",
            success(data) {
                // 當訪問yyy成功時訪問zzz
                $.ajax({
                    url: "https://zzz",
                    success(data) {
                        // 一直回調下去...
                    },
                    error() {}
                })
            },
            error() {}
        })
    },
    error() {}
})

上面的代碼里對于yyy的訪問是基于xxx訪問成功的前提下,而zzz的訪問是基于yyy訪問成功的前提下,一直這樣下去,代碼會十分混亂,所以為了美化這種代碼,提出了兩種解決方案:

  • Promise對象(其實是ES5.5的內容)
  • async/await關鍵字
Promise對象

用于封裝異步操作,使用步驟如下:
1.實例化一個Promise()對象
2.將所有異步操作封裝到Promise()對象內部,里面傳入兩個參數,分別是執行成功和失敗的回調函數
3.通過Promise()對象的then()方法調用異步操作時執行的成功和失敗的回調函數
舉例:

let p = new Promise(function(resolve, reject) {
  // 封裝異步操作,傳入成功時執行的resolve函數和失敗執行的reject函數
  $.ajax({
    url: "https://xxx",
    success(data) {
      resolve("success", data);
      // 成功的話執行resolve函數,并傳入一個狀態和對應的數據,在后面then的時候調用
    },
    error(data) {
      reject("error", data);
      // 失敗執行reject函數,在后面catch的時候調用
    }
  });
});

// 在這里進行異步操作的回調處理
p.then(function(status, data) {
  // 成功時的回調函數,相當于此時執行resolve方法
  console.log(status, ":", data);
}).catch(function(status, data) {
  // 失敗時的回調函數,相當于此時執行reject方法
  console.log(status, ":", data);
});

從上面代碼可以看出,此時整個異步操作被放在了一個對象p里,然后后續只需要調用p.then().catch()來進行異步操作的回調即可,相比之前的來說,這個顯然看起來更加直觀

Promise統一封裝

Promise對象下有一個all方法,可以將所有的異步操作通過數組方式傳入,然后當所有異步操作都執行成功或存在失敗操作時通過then方法調用后續操作,舉例:

new Promise.all([
    // 傳入一個包含多個異步操作的數組
    $.ajax({ url: "https://xxx", dataType: "json"}),
    $.ajax({ url: "https://yyy", dataType: "json"}),
    $.ajax({ url: "https://zzz", dataType: "json"}),
]).then(
    // 通過then方法調用后續操作,分別傳入一個成功和失敗的處理方法
    ([data1, data2, data3]) => {
        // 返回的數組分別是data1,data2,data3,通過結構方式接受
        console.log(data1, data2, data3);
    }, res => {
        console.log("失敗");
    })
Promise異步擇優

Promise下有race方法和all方法操作類似,也是傳入一個異步操作數組,然后當最快的一個異步操作完成時則調用then方法進行后續操作,并忽視其他的異步操作,若所有操作都失敗才會執行錯誤方法,常用于類似CDN加速時選擇最快的鏈接場景,舉例:

Promise.race([
    $.ajax({ url: "https://xxx", dataType: "json"}),
    $.ajax({ url: "https://yyy", dataType: "json"}),
    $.ajax({ url: "https://zzz", dataType: "json"}),
]).then(
    (data) => {
        // 只對第一個完成的操作進行回調
        console.log(data);
    }, res => {
        console.log("失敗");
    })
async/await關鍵字

前面的Promise對象雖然讓回調代碼更加直觀,但在封裝時感覺還是挺不簡潔的,而asyncawait可以讓異步的代碼更加直觀,能使我們編寫異步代碼時更加貼近于同步代碼的寫法。使用這兩個關鍵字可以控制異步操作是否暫停,async關鍵字用來聲明操作中存在異步,只需在操作前聲明這個關鍵字即可(注意聲明了async的函數的返回值將會自動轉為promise對象),而在async關鍵字下的異步操作如果存在需要等待的操作,可以使用await關鍵字聲明,舉例:

async function aaa() {
    console.log(0);
    let x = await $.ajax({ url: "https://xxx", dataType: "json" }, function(data) { console.log(data); })
    // x接收異步操作返回的數據
    // 由于這里使用了await等待,所以在該步會等待異步操作執行
    // 如果異步操作失敗成功則執行后續步驟,否則不會進行后面的操作
    console.log(1);
    let y = await $.ajax({ url: "https://yyy", dataType: "json" }, function(data) { console.log(data); })
    console.log(2);
    let z = await $.ajax({ url: "https://zzz", dataType: "json" }, function(data) { console.log(data); })
    console.log(3);
}

上面是添加了asyncawait關鍵字以后的代碼,其和沒添加的代碼結果有什么區別呢?如果是沒添加這兩個關鍵字的的情況,那么結果異步操作不管是否執行成功,一般都是先依次輸出0/1/2,然后再輸出三個異步請求的回調方法(成功或者失敗);而添加了關鍵字以后,就會先去等待第一個await的地方的執行,并且判斷執行是否成功,如果成功則繼續往后執行,否則就報錯不繼續執行,所以就會先輸出0,然后等待一段時間,當第一個await地方執行成功則繼續往后輸出1,否則報錯后停止,以此類推...
注:
從上面可以看出代碼相比使用Promise對象要更加的簡潔,而其返回的對象(上面變量x、y、z)接收的內容,實際上還是Promise對象,所以可以理解成這兩個關鍵字就是對Promise對象的進一步封裝。
注2:
asyncawait關鍵字組合操作的原理就是將函數拆分成一個個小的存在依賴關系的函數執行,比如上面的實際上就可以看成如下代碼:

function aaa() {
    console.log(0);
    let x = $.ajax({
        url: "https://xxx",
        dataType: "json"
    }, function(data) {
        console.log(data);
        // 第一個await執行成功則執行后續內容
        console.log(1);
        let y = $.ajax({
            url: "https://yyy",
            dataType: "json"
        }, function(data) {
            console.log(data);
            // 第二個await執行成功則執行后續內容
            console.log(2);
            let z = $.ajax({
                url: "https://zzz",
                dataType: "json"
            }, function(data) {
                console.log(data);
                // 第三個await執行成功則執行后續內容
                console.log(3);
            })
        })
    })
}

可以看出這個和最開始回調地獄的代碼一樣,但是通過兩個關鍵字修飾了以后,達到了簡化代碼的目的
注3:
對于異步失敗的操作處理,如ajax,此時如果請求失敗是不會有返回的,比如下面代碼:

async function aaa() {
  console.log(0);
  let x = await $.ajax({ url: "https://xxx", dataType: "json" }, function(data) { console.log(data); })
  // 這里如果請求失敗將會沒有返回值,此時x就會變成undefined,導致下面那句報錯
  console.log(x);
}

所以為了能夠對失敗的請求進行處理,我們可以使用try{}catch(e){}來捕捉處理,舉例:

async function aaa() {
  console.log(0);
  try{
    let x = await $.ajax({ url: "https://xxx", dataType: "json" }, function(data) { console.log(data); })
    console.log(x);
  } catch(e) {
    // 捕捉失敗的請求
    console.log(e);
  }
}

或者封裝到Promise對象里操作也可以,舉例:

function ajax() {
  // 封裝成Promise對象
  return new Promise((resolve, reject) =>
    $.ajax({
      url: "https://xxx",
      dataType: "json",
      success(data) {
        // then里調用
        resolve(data);
      },
      error(e) {
        // 失敗時catch捕捉
        reject(e);
      }
    })
  );
}

async function aaa() {
  console.log(0);
  let x = await ajax().then(data => data).catch(e => false);
  // 異步成功則只執行then,返回data,失敗則捕捉并返回false
  console.log(x);
}
更多參考

http://es6.ruanyifeng.com/#docs/module

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容

  • es6相較之前es5,增加了許多新的特性,提高了javascript體驗,我在es6學習和使用的過程中進行了紀錄。...
    YomonAh閱讀 230評論 0 2
  • 本文為阮一峰大神的《ECMAScript 6 入門》的個人版提純! babel babel負責將JS高級語法轉義,...
    Devildi已被占用閱讀 1,999評論 0 4
  • 第一章:塊級作用域綁定 塊級聲明 1.var聲明及變量提升機制:在函數作用域或者全局作用域中通過關鍵字var聲明的...
    BeADre_wang閱讀 847評論 0 0
  • [TOC] 參考阮一峰的ECMAScript 6 入門參考深入淺出ES6 let和const let和const都...
    郭子web閱讀 1,799評論 0 1
  • 你不知道JS:異步 第三章:Promises 接上篇3-1 錯誤處理(Error Handling) 在異步編程中...
    purple_force閱讀 1,410評論 0 2