本次的ES6語法的匯總總共分為上、中、下三篇,本篇文章為中篇。
匯總上篇文章請(qǐng)戳這里--談?wù)凟S6語法(匯總上篇)
好了,我們直奔中篇的內(nèi)容~
數(shù)組擴(kuò)展
數(shù)組擴(kuò)展運(yùn)算符
數(shù)組擴(kuò)展運(yùn)算符(spread)是三個(gè)點(diǎn)(...)。它好比rest參數(shù)的逆運(yùn)算,將一個(gè)數(shù)組轉(zhuǎn)為用空格分隔的參數(shù)序列。
console.log(...[1, 2, 3]); // 1 2 3
console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5
??rest參數(shù)是運(yùn)用在函數(shù)參數(shù)上的,將函數(shù)參數(shù)轉(zhuǎn)換為數(shù)組的形式,如下:
function fn(...values) {
console.log(values); // ['jia', 'ming']
}
fn('jia', 'ming');
下面我們結(jié)合數(shù)組擴(kuò)展運(yùn)算符和rest參數(shù)來實(shí)現(xiàn)一個(gè)類似call的方法call2操作:
Function.prototype.call2 = function(context, ...args){ // 這里使用到rest參數(shù)
context = context || window; // 因?yàn)閭鬟f過來的context有可能是null
context.fn = this; // 讓fn的上下文為context
const result = context.fn(...args); // 這里使用了數(shù)組擴(kuò)展運(yùn)算符
delete context.fn;
return result; // 因?yàn)橛锌赡躷his函數(shù)會(huì)有返回值return
}
var job = 'outter teacher';
var obj = {
job: 'inner teacher'
};
function showJob() {
console.log(this.job);
}
showJob(); // outter teacher
showJob.call2(obj); // inner teacher
復(fù)習(xí)一下,我們把var job = 'outter teacher'改為let job = 'outter teacher'后,showJob()會(huì)輸出什么?
答案是undefined。在前一篇中也提到過,ES6語法聲明的變量是不會(huì)掛載在全局對(duì)象上的~
Array.from()
Array.from方法用于將兩類對(duì)象轉(zhuǎn)為真正的數(shù)組:類似數(shù)組的對(duì)象(array-like object)和可遍歷(iterable)的對(duì)象(對(duì)象包括ES6新增的數(shù)據(jù)結(jié)構(gòu)Set和Map)。
// 類數(shù)組轉(zhuǎn)化成數(shù)組
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()方法用于將一組值,轉(zhuǎn)換為數(shù)組。
let arr = Array.of(2, 3, 'reng');
console.log(arr); // [2, 3, 'reng']
console.log(arr.pop()); // reng
Array.of基本上可以彌補(bǔ)Array()或new Array()帶來的因?yàn)閰?shù)個(gè)數(shù)導(dǎo)致的不同行為。Array.of基本上可以替代它們兩個(gè)了。
Array.of(); // []
Array.of('reng'); // ['reng']
Array.of(2, 'reng'); // [2, 'reng']
數(shù)組中還有其它有用的方法:
- copyWithin(target, start = 0, end = this.length): 拷貝指定數(shù)組的范圍值
- find(fn): 用于查找第一個(gè)符合條件的數(shù)組成員,沒有返回undefined
- findIndex(fn): 用于查找第一個(gè)符合條件的數(shù)組成員的位置,沒有返回-1
- entries(): 對(duì)鍵值對(duì)的遍歷
- keys(): 對(duì)鍵的遍歷
- values(): 對(duì)值的遍歷
- includes(el): 返回一個(gè)布爾值,表示某個(gè)數(shù)組是否包含給定的值,與字符串的
include(el)方法相似 - flat(num): 將嵌套的數(shù)組拉平,num是遍歷的深度
[1, [2, [3]]].flat(Infinity);
// [1, 2, 3]
有這么一個(gè)需求:將數(shù)組[[2, 8], [2], [[4, 6], 7, 6]]轉(zhuǎn)成一維且元素不重復(fù)的數(shù)組。
我們的實(shí)現(xiàn)方案如下:
let arr = [[2, 8], [2], [[4, 6], 7, 6]];
console.log([...new Set(arr.flat(Infinity))]); // [2, 8, 4, 6, 7]
對(duì)象擴(kuò)展
屬性名表達(dá)式
ES6允許字面量定義對(duì)象時(shí),把表達(dá)式放在方括號(hào)內(nèi):
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'] // '!'
對(duì)象的擴(kuò)展運(yùn)算符
上面整理數(shù)組擴(kuò)展內(nèi)容的時(shí)候,提到了數(shù)組的擴(kuò)展運(yùn)算符。ES2018將這個(gè)運(yùn)算符引入了對(duì)象~
let z = { a: 3, b: 4 };
let n = { ...z }; // 關(guān)鍵點(diǎn)
n // { a: 3, b: 4 }
對(duì)象中某些新增的方法
- Object.is(arg1, arg2): 比較兩個(gè)值是否嚴(yán)格相等,與
===行為基本一致 - Object.assign(target, source1, ...): 用于對(duì)象的合并,將源對(duì)象(source)的所有可枚舉屬性,復(fù)制到目標(biāo)對(duì)象(target)。屬于淺拷貝
- Object.keys(obj): 返回一個(gè)數(shù)組,成員是參數(shù)對(duì)象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名
- Object.values(obj): 方法返回一個(gè)數(shù)組,成員是參數(shù)對(duì)象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值。
- Object.entries(obj): 方法返回一個(gè)數(shù)組,成員是參數(shù)對(duì)象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對(duì)數(shù)組。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
Set和Map數(shù)據(jù)結(jié)構(gòu)
Set
Set翻譯出來就是集合,有元素唯一性的特點(diǎn)。
在數(shù)組去重的場景上很有用處:
// 去除數(shù)組的重復(fù)成員
[...new Set(array)]
// 如
console.log([...new Set([2, 2, 3, 2])]); // [2, 3]
需要留意的Set屬性和方法有以下:
- size: 返回實(shí)例成員的總數(shù)
- add(value): 添加某個(gè)值,返回Set結(jié)構(gòu)本身
- delete(value): 刪除某個(gè)值,返回一個(gè)布爾值,表示刪除是否成功。
- has(value): 返回一個(gè)布爾值,表示該值是否為Set的成員
- clear(): 清除所有成員,沒有返回值。
- key():返回鍵名的遍歷器。
- values(): 返回鍵值的遍歷器。
- entries(): 返回鍵值對(duì)的遍歷器。
- forEach(): 使用回調(diào)函數(shù)遍歷每個(gè)成員
WeakSet
WeakSet結(jié)構(gòu)與Set類似,也是有不重復(fù)元素的集合。但是它和Set有兩個(gè)區(qū)別:
WeakSet對(duì)象中只能存放對(duì)象引用, 不能存放值, 而Set對(duì)象都可以.WeakSet中對(duì)象中存儲(chǔ)的對(duì)象值都是被弱引用的, 如果沒有其他的變量或?qū)傩砸眠@個(gè)對(duì)象值, 則這個(gè)對(duì)象值會(huì)被當(dāng)成垃圾回收掉. 正因?yàn)檫@樣, WeakSet 對(duì)象是無法被枚舉的, 沒有辦法拿到它包含的所有元素。
var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false, 對(duì)象 foo 并沒有被添加進(jìn) ws 中
ws.delete(window); // 從集合中刪除 window 對(duì)象
ws.has(window); // false, window 對(duì)象已經(jīng)被刪除了
ws.clear(); // 清空整個(gè) WeakSet 對(duì)象
WeakSet 沒有size屬性,沒有辦法遍歷它的成員。
Map
Map對(duì)象保持鍵值對(duì)。任何值(對(duì)象或者原始值)都可以作為一個(gè)鍵或一個(gè)值。
Object和Map的比較:
- 一個(gè)
Object的鍵只能是字符串或者Symbols,但一個(gè)Map的鍵可以是任意值,包括函數(shù)、對(duì)象、基本類型。 -
Map中的鍵值是有序的,而添加到對(duì)象中的鍵則不是。因此,當(dāng)對(duì)它進(jìn)行遍歷時(shí),Map對(duì)象是按插入的順序返回鍵值。 -
Map在涉及頻繁增刪鍵值對(duì)的場景下會(huì)有些性能優(yōu)勢`。 - ...
如果你需要“鍵值對(duì)”的數(shù)據(jù)結(jié)構(gòu),Map比Object更合適。
const set = new Set([ // 數(shù)組轉(zhuǎn)換為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方法設(shè)置鍵名key對(duì)應(yīng)的鍵值為value,然后返回整個(gè) Map 結(jié)構(gòu)。如果key已經(jīng)有值,則鍵值會(huì)被更新,否則就新生成該鍵。
- get(key):get方法讀取key對(duì)應(yīng)的鍵值,如果找不到key,返回undefined
WeakMap
WeakMap結(jié)構(gòu)與Map結(jié)構(gòu)類似,也是用于生成鍵值對(duì)的集合。但是有兩點(diǎn)區(qū)別:
-
WeakMap只接受對(duì)象作為鍵名(null除外),不接受其他類型的值作為鍵名。 -
WeakMap的鍵名所指向的對(duì)象,不計(jì)入垃圾回收機(jī)制。和WeakSet相似啦。
屬性方法啥的跟Map差不多,就是沒有了size和forEach,因?yàn)槠涫遣豢擅杜e的。
Promise對(duì)象
Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案“回調(diào)函數(shù)和事件”更合理和更強(qiáng)大。
Promise對(duì)象有以下兩個(gè)特點(diǎn):
對(duì)象的狀態(tài)不受外界影響。
Promise對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失?。?。一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果。
Promise對(duì)象的狀態(tài)改變,只有兩種情況:從pending變成fulfilled(fulfilled也稱resolved)和從pending變成rejected。
用法
const promise = new Promise(function(resolve, reject) {
// ...some code
if(/* 異步操作成功 */) {
resolve(value);
} else {
reject(error);
}
})
參數(shù)resolve和reject是兩個(gè)函數(shù),由JavaScript引擎提供,不用自己部署。
Promise實(shí)例生成之后,可以使用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
我們來粘貼個(gè)簡單例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value); // 100ms后輸出'done'
});
嗯~我們順道來復(fù)習(xí)下setTimeout的第三個(gè)參數(shù)。哦??,不,是第三個(gè),第四個(gè)...
var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);
-
function是你想要在到期時(shí)間(delay毫秒)之后執(zhí)行的函數(shù)。 -
delay是可選語法,表示延遲的毫秒數(shù)。 -
param1, ..., paramN是可選的附加參數(shù),一旦定時(shí)器到期,它們會(huì)作為參數(shù)傳遞給function
那么,到這里你理解了上面的例子為什么在100ms后輸出done了嘛??
詳細(xì)的setTimeout信息,請(qǐng)戳MDN的setTimeout。
簡單的例子看完了,看下我們?cè)诠ぷ髦惺褂玫帽容^多的請(qǐng)求接口的例子:
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作為參數(shù)傳給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)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err)); // promise中任何一個(gè)拋出錯(cuò)誤,都會(huì)被最后一個(gè)catch捕獲
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log('rejected:', err));
finally方法
Promise.prototype.finally()方法(其不接受任何參數(shù))用于指定不管Promise對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。該方法是 ES2018 引入標(biāo)準(zhǔn)的。
語法:
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
Promise.all
構(gòu)造函數(shù)方法Promise.all方法用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例。
const p = Promise.all([p1, p2, p3]);
上面代碼中,Promise.all方法接受一個(gè)數(shù)組作為參數(shù),p1, p2, p3都是Promise實(shí)例。如果不是會(huì)調(diào)用Promise.resolve方法,具體看文檔。
// 生成一個(gè)Promise對(duì)象的數(shù)組
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 個(gè) Promise 實(shí)例的數(shù)組,只有這6個(gè)實(shí)例的狀態(tài)都變成fulfilled,或者其中有一個(gè)變?yōu)?code>rejected,才會(huì)調(diào)用Promise.all方法后面的回調(diào)函數(shù)。
??注意,如果作為參數(shù)的Promise實(shí)例,自己定義了catch方法,那么它一旦被rejected,并不會(huì)觸發(fā)Promise.all()的catch方法。所以使用Promise.all()別手癢在每個(gè)實(shí)例promise內(nèi)添加錯(cuò)誤捕獲。
一道練手題
需求:使用promise改寫下面的代碼,使得輸出的期望結(jié)果是每隔一秒輸出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對(duì)象
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語法的匯總總共分為上、中、下三篇,本篇文章為中篇。
文章首發(fā)在github上--談?wù)凟S6語法(匯總中篇)。更多的內(nèi)容,請(qǐng)戳我的博客進(jìn)行了解,能留個(gè)star就更好了??