本次的ES6語法的彙總總共分為上、中、下三篇,本篇文章為中篇。
彙總上篇文章請戳這裡--談談ES6語法(彙總上篇)
好了,我們直奔中篇的內容~
陣列擴充套件
陣列擴充套件運算子
陣列擴充套件運算子(spread)是三個點(...
)。它好比rest引數的逆運算,將一個陣列轉為用空格分隔的引數序列。
console.log(...[1, 2, 3]); // 1 2 3
console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5
複製程式碼
⚠️rest引數是運用在函式引數上的,將函式引數轉換為陣列的形式,如下:
function fn(...values) {
console.log(values); // ['jia', 'ming']
}
fn('jia', 'ming');
複製程式碼
下面我們結合陣列擴充套件運算子和rest引數來實現一個類似call
的方法call2操作:
Function.prototype.call2 = function(context, ...args){ // 這裡使用到rest引數
context = context || window; // 因為傳遞過來的context有可能是null
context.fn = this; // 讓fn的上下文為context
const result = context.fn(...args); // 這裡使用了陣列擴充套件運算子
delete context.fn;
return result; // 因為有可能this函式會有返回值return
}
var job = 'outter teacher';
var obj = {
job: 'inner teacher'
};
function showJob() {
console.log(this.job);
}
showJob(); // outter teacher
showJob.call2(obj); // inner teacher
複製程式碼
複習一下,我們把var job = 'outter teacher'
改為let job = 'outter teacher'
後,showJob()
會輸出什麼?
答案是undefined
。在前一篇中也提到過,ES6語法宣告的變數是不會掛載在全域性物件上的~
Array.from()
Array.from
方法用於將兩類物件
轉為真正的陣列:類似陣列的物件(array-like object)和可遍歷(iterable)的物件(物件包括ES6新增的資料結構Set和Map)。
// 類陣列轉化成陣列
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}
// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
複製程式碼
Array.of()
Array.of()
方法用於將一組值,轉換為陣列。
let arr = Array.of(2, 3, 'reng');
console.log(arr); // [2, 3, 'reng']
console.log(arr.pop()); // reng
複製程式碼
Array.of
基本上可以彌補Array()或new Array()
帶來的因為引數個數導致的不同行為。Array.of
基本上可以替代它們兩個了。
Array.of(); // []
Array.of('reng'); // ['reng']
Array.of(2, 'reng'); // [2, 'reng']
複製程式碼
陣列中還有其它有用的方法:
- copyWithin(target, start = 0, end = this.length): 拷貝指定陣列的範圍值
- find(fn): 用於查詢第一個符合條件的陣列成員,沒有返回undefined
- findIndex(fn): 用於查詢第一個符合條件的陣列成員的位置,沒有返回-1
- entries(): 對鍵值對的遍歷
- keys(): 對鍵的遍歷
- values(): 對值的遍歷
- includes(el): 返回一個布林值,表示某個陣列是否包含給定的值,與字串的
include(el)
方法相似 - flat(num): 將巢狀的陣列拉平,num是遍歷的深度
[1, [2, [3]]].flat(Infinity);
// [1, 2, 3]
複製程式碼
有這麼一個需求:將陣列[[2, 8], [2], [[4, 6], 7, 6]]轉成一維且元素不重複的陣列。
我們的實現方案如下:
let arr = [[2, 8], [2], [[4, 6], 7, 6]];
console.log([...new Set(arr.flat(Infinity))]); // [2, 8, 4, 6, 7]
複製程式碼
物件擴充套件
屬性名錶達式
ES6允許字面量定義物件時,把表示式放在方括號內:
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world',
['end'+'symbol']: '!'
};
a['first word'] // 'hello'
a[lastWord] // 'world'
a['last word'] // 'world'
a['endsymbol'] // '!'
複製程式碼
物件的擴充套件運算子
上面整理陣列擴充套件內容的時候,提到了陣列的擴充套件運算子。ES2018
將這個運算子引入了物件~
let z = { a: 3, b: 4 };
let n = { ...z }; // 關鍵點
n // { a: 3, b: 4 }
複製程式碼
物件中某些新增的方法
- Object.is(arg1, arg2): 比較兩個值是否嚴格相等,與
===
行為基本一致 - Object.assign(target, source1, ...): 用於物件的合併,將源物件(source)的所有可列舉屬性,複製到目標物件(target)。屬於淺拷貝
- Object.keys(obj): 返回一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名
- Object.values(obj): 方法返回一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值。
- Object.entries(obj): 方法返回一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對陣列。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
複製程式碼
Set和Map資料結構
Set
Set
翻譯出來就是集合,有元素唯一性的特點。
在陣列去重的場景上很有用處:
// 去除陣列的重複成員
[...new Set(array)]
// 如
console.log([...new Set([2, 2, 3, 2])]); // [2, 3]
複製程式碼
需要留意的Set屬性和方法有以下:
- size: 返回例項成員的總數
- add(value): 新增某個值,返回Set結構本身
- delete(value): 刪除某個值,返回一個布林值,表示刪除是否成功。
- has(value): 返回一個布林值,表示該值是否為Set的成員
- clear(): 清除所有成員,沒有返回值。
- key():返回鍵名的遍歷器。
- values(): 返回鍵值的遍歷器。
- entries(): 返回鍵值對的遍歷器。
- forEach(): 使用回撥函式遍歷每個成員
WeakSet
WeakSet
結構與Set
類似,也是有不重複元素的集合。但是它和Set有兩個區別:
-
WeakSet
物件中只能存放物件引用, 不能存放值, 而Set
物件都可以. -
WeakSet中
物件中儲存的物件值都是被弱引用的, 如果沒有其他的變數或屬性引用這個物件值, 則這個物件值會被當成垃圾回收掉. 正因為這樣, WeakSet 物件是無法被列舉的, 沒有辦法拿到它包含的所有元素。
var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false, 物件 foo 並沒有被新增進 ws 中
ws.delete(window); // 從集合中刪除 window 物件
ws.has(window); // false, window 物件已經被刪除了
ws.clear(); // 清空整個 WeakSet 物件
複製程式碼
WeakSet 沒有size屬性,沒有辦法遍歷它的成員。
Map
Map
物件保持鍵值對。任何值(物件或者原始值)都可以作為一個鍵或一個值。
Object和Map的比較:
- 一個
Object
的鍵只能是字串或者Symbols
,但一個Map
的鍵可以是任意值,包括函式、物件、基本型別。 Map
中的鍵值是有序的,而新增到物件中的鍵則不是。因此,當對它進行遍歷時,Map
物件是按插入的順序返回鍵值。Map
在涉及頻繁增刪鍵值對的場景下會有些效能優勢`。- ...
如果你需要“鍵值對”的資料結構,Map
比Object
更合適。
const set = new Set([ // 陣列轉換為map
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
複製程式碼
Map擁有的屬性和方法和Set相似,多出了些:
- set(key, value):set方法設定鍵名key對應的鍵值為value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。
- get(key):get方法讀取key對應的鍵值,如果找不到key,返回undefined
WeakMap
WeakMap
結構與Map
結構類似,也是用於生成鍵值對的集合。但是有兩點區別:
WeakMap
只接受物件作為鍵名(null除外),不接受其他型別的值作為鍵名。WeakMap
的鍵名所指向的物件,不計入垃圾回收機制。和WeakSet
相似啦。
屬性方法啥的跟Map
差不多,就是沒有了size
和forEach
,因為其是不可列舉的。
Promise物件
Promise
是非同步程式設計的一種解決方案,比傳統的解決方案“回撥函式和事件”更合理和更強大。
Promise
物件有以下兩個特點:
-
物件的狀態不受外界影響。
Promise
物件代表一個非同步操作,有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。 -
一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
Promise
物件的狀態改變,只有兩種情況:從pending
變成fulfilled(fulfilled也稱resolved)
和從pending
變成rejected
。
用法
const promise = new Promise(function(resolve, reject) {
// ...some code
if(/* 非同步操作成功 */) {
resolve(value);
} else {
reject(error);
}
})
複製程式碼
引數resolve
和reject
是兩個函式,由JavaScript引擎提供,不用自己部署。
Promise
例項生成之後,可以使用then
方法分別指定resolved
狀態和rejected
狀態的回撥函式。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
複製程式碼
我們來貼上個簡單例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value); // 100ms後輸出'done'
});
複製程式碼
嗯~我們順道來複習下setTimeout
的第三個引數。哦?,不,是第三個,第四個...
var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);
複製程式碼
function
是你想要在到期時間(delay
毫秒)之後執行的函式。delay
是可選語法,表示延遲的毫秒數。param1, ..., paramN
是可選的附加引數,一旦定時器到期,它們會作為引數傳遞給function
那麼,到這裡你理解了上面的例子為什麼在100ms
後輸出done
了嘛?
詳細的setTimeout
資訊,請戳MDN的setTimeout。
簡單的例子看完了,看下我們在工作中使用得比較多的請求介面的例子:
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if(this.readyState !== 4) {
return;
}
if(this.status === 200) {
resolve(this.response); // this.response作為引數傳給then中的json
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = handler;
client.responseType = 'json';
client.setRequestHeader('Accept', 'application.json');
client.send();
});
return promise;
};
getJSON('/post.json').then(function(json) {
console.log('Contents: '+ json);
}, function(error) {
console.log('error happen ', error);
});
複製程式碼
catch方法
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的別名,用於指定發生錯誤時的回撥函式。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err)); // promise中任何一個丟擲錯誤,都會被最後一個catch捕獲
// 等同於
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log('rejected:', err));
複製程式碼
finally方法
Promise.prototype.finally()
方法(其不接受任何引數)用於指定不管Promise
物件最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。
語法:
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
複製程式碼
Promise.all
建構函式方法Promise.all
方法用於將多個Promise
例項,包裝成一個新的Promise
例項。
const p = Promise.all([p1, p2, p3]);
複製程式碼
上面程式碼中,Promise.all
方法接受一個陣列作為引數,p1, p2, p3
都是Promise
例項。如果不是會呼叫Promise.resolve
方法,具體看文件。
// 生成一個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
方法後面的回撥函式。
⚠️注意,如果作為引數的Promise
例項,自己定義了catch
方法,那麼它一旦被rejected
,並不會觸發Promise.all()
的catch
方法。所以使用Promise.all()
別手癢在每個例項promise內新增錯誤捕獲。
一道練手題
需求:使用promise改寫下面的程式碼,使得輸出的期望結果是每隔一秒輸出0, 1, 2, 3, 4, 5
,其中i < 5
條件不能變
for(var i = 0 ; i < 5; i++){
setTimeout(function(){
console.log(i);
},1000)
}
console.log(i);
複製程式碼
我們直接上使用promise改寫的程式碼吧~
const tasks = []; // 存放promise物件
for(let i = 0; i < 5; i++){
tasks.push(new Promise((resolve) => {
setTimeout(() => {
console.log(i);
resolve();
}, 1000 * i);
}));
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(tasks.length);
}, 1000);
});
// 每隔一秒輸出 0, 1, 2, 3, 4, 5
複製程式碼
參考和後話
本次的ES6語法的彙總總共分為上、中、下三篇,本篇文章為中篇。
文章首發在github上--談談ES6語法(彙總中篇)。更多的內容,請戳我的部落格進行了解,能留個star就更好了?