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.
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
出來之前,異步請求主要還是用ajax
和axios
(axios
相當于結合ajax
和promise
后再封裝了一層)。fetch
也是基于promise
管理(因此可以像promise
對象那樣處理),并且是JS原生的內置類,所以可以直接使用,而無需像ajax
和axios
那樣還需要先導入一個庫。并且fetch
底層不是基于XMLHttpRequest
實現的,官方的意思是用了一種更好的方案實現,不過fetch
不支持老版本IE,并且fetch
和原來的ajax
、axios
不一樣,他只要有服務端返回就算訪問成功(別的會根據狀態碼等來判斷),因此只有網絡連接失敗時才算是訪問失敗。
使用
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生成的文件后可以發現輸出了a
和b
的值。
注:
注意相對路徑需要加./
這樣的定位符,因為模塊化需要在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對象雖然讓回調代碼更加直觀,但在封裝時感覺還是挺不簡潔的,而async
和await
可以讓異步的代碼更加直觀,能使我們編寫異步代碼時更加貼近于同步代碼的寫法。使用這兩個關鍵字可以控制異步操作是否暫停,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);
}
上面是添加了async
和await
關鍵字以后的代碼,其和沒添加的代碼結果有什么區別呢?如果是沒添加這兩個關鍵字的的情況,那么結果異步操作不管是否執行成功,一般都是先依次輸出0
/1
/2
,然后再輸出三個異步請求的回調方法(成功或者失敗);而添加了關鍵字以后,就會先去等待第一個await
的地方的執行,并且判斷執行是否成功,如果成功則繼續往后執行,否則就報錯不繼續執行,所以就會先輸出0
,然后等待一段時間,當第一個await
地方執行成功則繼續往后輸出1
,否則報錯后停止,以此類推...
注:
從上面可以看出代碼相比使用Promise
對象要更加的簡潔,而其返回的對象(上面變量x、y、z)接收的內容,實際上還是Promise
對象,所以可以理解成這兩個關鍵字就是對Promise
對象的進一步封裝。
注2:
async
和await
關鍵字組合操作的原理就是將函數拆分成一個個小的存在依賴關系的函數執行,比如上面的實際上就可以看成如下代碼:
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);
}