談談ES6語法(彙總中篇)

call_me_R發表於2019-07-13

本次的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有兩個區別:

  1. WeakSet物件中只能存放物件引用, 不能存放值, 而Set物件都可以.

  2. 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在涉及頻繁增刪鍵值對的場景下會有些效能優勢`。
  • ...

如果你需要“鍵值對”的資料結構,MapObject更合適。

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差不多,就是沒有了sizeforEach,因為其是不可列舉的。

Promise物件

Promise是非同步程式設計的一種解決方案,比傳統的解決方案“回撥函式和事件”更合理和更強大。

Promise物件有以下兩個特點:

  1. 物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。

  2. 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種情況:從pending變成fulfilled(fulfilled也稱resolved)和從pending變成rejected

用法

const promise = new Promise(function(resolve, reject) {
	// ...some code
	
	if(/* 非同步操作成功 */) {
		resolve(value);
	} else {
		reject(error);
	}
})
複製程式碼

引數resolvereject是兩個函式,由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就更好了?

相關文章