前端進階-ES6內建功能
Symbol
Symbol 簡介
Symbol 是 JS 原始資料型別列表中(numbers
、strings
、booleans
、null
、undefined
)的最新補充。Symbol
是一個唯一識別符號,常用於唯一標識物件中的屬性。
碗這個物件中有幾個水果屬性(水果也是物件),當有兩個相同的水果在碗裡時,會出現混亂,我們不知道什麼時候拿哪一個,這就是問題。我們需要一種方式來唯一的標識這這些香蕉。
Symbol 識別符號
Symbol
是一種獨特的且不可變
的資料型別,經常用來標識物件屬性
。
const sym1 = Symbol('apple');
console.log(sym1); // Symbol(apple)
它將建立唯一的識別符號,並將其儲存在 sym1
中。描述 “apple
” 只是用來描述識別符號的一種方式,但是不能用來訪問識別符號本身
。
const sym2 = Symbol('banana');
const sym3 = Symbol('banana');
console.log(sym2 === sym3); // false
描述只是用來描述符號
,它並不是識別符號本身的一部分
。無論描述是什麼,每次都建立新的識別符號
。
示例說明,下面是代表上圖中的 bowl(碗)的程式碼
const bowl = {
'apple': { color: 'red', weight: 136.078 },
'banana': { color: 'yellow', weight: 183.15 },
'orange': { color: 'orange', weight: 170.097 }
};
碗中包含水果,它們是 bowl 的屬性物件。但是,當我們新增第二個香蕉時,遇到了問題。
const bowl = {
'apple': { color: 'red', weight: 136.078 },
'banana': { color: 'yellow', weight: 183.151 },
'orange': { color: 'orange', weight: 170.097 },
'banana': { color: 'yellow', weight: 176.845 }
};
console.log(bowl);
// Object {apple: Object, banana: Object, orange: Object}
新新增的香蕉將上一個香蕉覆蓋
了。為了解決該問題,我們可以使用識別符號。
const bowl = {
[Symbol('apple')]: { color: 'red', weight: 136.078 },
[Symbol('banana')]: { color: 'yellow', weight: 183.15 },
[Symbol('orange')]: { color: 'orange', weight: 170.097 },
[Symbol('banana')]: { color: 'yellow', weight: 176.845 }
};
console.log(bowl);
// Object {Symbol(apple): Object, Symbol(banana): Object, Symbol(orange): Object, Symbol(banana): Object}
通過更改 bowl 的屬性並使用識別符號,每個屬性都是唯一的識別符號,第一個香蕉不會被第二個香蕉覆蓋。
迭代器協議和可迭代協議
ES6 中的兩個新協議:
- 可迭代協議
- 迭代器協議
這兩個協議不是內建的,但是它們可以幫助你理解 ES6 中的新迭代概念,就像給你展示識別符號的使用案例一樣。
可迭代協議
可迭代協議用來定義和自定義物件的迭代行為
。也就是說在 ES6 中,你可以靈活地指定迴圈訪問物件中的值的方式
。對於某些物件,它們已經內建了這一行為。例如,字串
和陣列
就是內建可迭代型別
的例子。
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (const digit of digits) {
console.log(digit);
}
任何可迭代的物件都可以使用新的 for...of
迴圈。
原理
為了使物件可迭代
,它必須實現可迭代介面
。介面其實就是為了讓物件可迭代,它必須包含預設的迭代器方法
。該方法將定義物件如何被迭代。
迭代器協議
迭代器協議用來定義物件生成一系列值的標準方式。實際上就是現在有了定義物件如何迭代的流程
。通過執行 .next()
方法來完成這一流程
。
原理
當物件執行 .next()
方法時,就變成了迭代器
。.next()
方法是無引數函式,返回具有兩個屬性的物件
:
value
:表示物件內值序列的下個值的資料done
:表示迭代器是否已迴圈訪問完值序列的布林值。如果done
為true
,則迭代器已到達值序列的末尾處;如果done
為false
,則迭代器能夠生成值序列中的另一個值。
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const arrayIterator = digits[Symbol.iterator]();
console.log(arrayIterator.next());
console.log(arrayIterator.next());
console.log(arrayIterator.next());
/**
Object {value: 0, done: false}
Object {value: 1, done: false}
Object {value: 2, done: false}
*/
Set
數學意義上的集合(Set)
Set 就是唯一項的集合。例如,{2, 4, 5, 6}
是 Set,因為每個數字都是唯一的,只出現一次。但是,{1, 1, 2, 4}
不是 Set,因為它包含重複的專案(1 出現了兩次!)。
Set(集合)
在 ES6 中,有一個新的內建物件的行為和數學意義上的集合相同
,使用起來類似於陣列
。這個新物件就叫做“Set”。
Set 與陣列之間的最大區別是:
- Set
不基於索引
,不能根據集合中的條目在集合中的位置引用這些條目 - Set 中的條目
不能單獨被訪問
基本上,Set 是讓你可以儲存唯一條目的物件
。你可以向 Set 中新增條目
,刪除條目
,並迴圈訪問
Set。這些條目可以是原始值
或物件
。
如何建立 Set
const games1 = new Set();
console.log(games1); // Set {},其中沒有條目
// 根據值列表建立 Set,則使用陣列
const games2 = new Set(['Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart', 'Super Mario Bros.']);
console.log(games2);
// Set {'Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart'}
// 會自動移除重複的條目 "Super Mario Bros.",很整潔!
修改 Set
const games = new Set(['Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart', 'Super Mario Bros.']);
games.add('Banjo-Tooie');
games.add('Age of Empires');
games.delete('Super Mario Bros.');
console.log(games);
// Set {'Banjo-Kazooie', 'Mario Kart', 'Banjo-Tooie', 'Age of Empires'}
games.clear()
console.log(games); // Set {}
.add()
新增不管成功與否,都會返回該 Set 物件
。另一方面,.delete()
則會返回一個布林值
,該值取決於是否成功刪除
(即如果該元素存在,返回 true
,否則返回 false
)。
使用 Set
.size
屬性可以返回 Set 中的條目數
:
const months = new Set(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']);
console.log(months.size); // 12
.has()
方法可以檢查 Set 中是否存在某個條目
。如果 Set 中有該條目,則 .has()
將返回 true
。如果 Set 中不存在該條目,則 .has()
將返回 false
。
console.log(months.has('September')); // true
.values()
方法可以返回 Set 中的值
。.values()
方法的返回值是 SetIterator
物件。
console.log(months.values());
/**
SetIterator {'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'}
*/
Set 與迭代器
ES6 中的新可迭代協議和迭代器協議中,Set 是內建可迭代型別
。這意味著迴圈時的兩件事:
- 可以使用 Set 的預設迭代器迴圈訪問 Set 中的每一項。
- 可以使用新的
for...of
迴圈來迴圈訪問 Set 中的每一項。
使用 SetIterator
因為 .values()
方法返回新的迭代器物件(稱為 SetIterator
),你可以將該迭代器物件儲存在變數中,並使用 .next()
訪問 Set 中的每一項。
const iterator = months.values();
iterator.next();
// Object {value: 'January', done: false}
iterator.next();
// Object {value: 'February', done: false}
// ...,一直執行到 done 等於 true 時,標誌著 Set 的結束。
使用for...of
迴圈
const colors = new Set(['red', 'orange', 'yellow', 'green', 'blue', 'violet', 'brown', 'black']);
for (const color of colors) {
console.log(color);
}
/**
red
orange
yellow
green
blue
violet
brown
black
*/
WeakSet(弱集合)
WeakSet 和普通 Set 很像,但是具有以下關鍵區別:
- WeakSet
只能包含物件
- WeakSet
無法迭代
,意味著不能迴圈訪問其中的物件 - WeakSet 沒有
.clear()
方法
const student1 = { name: 'James', age: 26, gender: 'male' };
const student2 = { name: 'Julia', age: 27, gender: 'female' };
const student3 = { name: 'Richard', age: 31, gender: 'male' };
const roster = new WeakSet([student1, student2, student3]);
console.log(roster);
/**
WeakSet {Object {name: 'Julia', age: 27, gender: 'female'}, Object {name: 'Richard', age: 31, gender: 'male'}, Object {name: 'James', age: 26, gender: 'male'}}
*/
// 新增物件以外的內容,系統將報錯!
roster.add('Amanda');
// Uncaught TypeError: Invalid value used in weak set(…)
垃圾回收
在 JavaScript 中,建立新的值時會分配記憶體,並且當這些值不再需要時,將自動釋放記憶體。這種記憶體不再需要後釋放記憶體的過程稱為垃圾回收。
WeakSet 通過專門使用物件作為鍵值來利用這一點。如果將物件設為 null
,則本質上是刪除該物件。當 JavaScript 的垃圾回收器執行時,該物件之前佔用的記憶體將被釋放,以便稍後在程式中使用。
student3 = null;
console.log(roster);
/**
WeakSet {Object {name: 'Julia', age: 27, gender: 'female'}, Object {name: 'James', age: 26, gender: 'male'}}
*/
這種機制的好處在於你不用去擔心要刪掉對 WeakSet 中已刪除物件的引用
,JavaScript 會幫你刪除!如果物件被刪除,當垃圾回收器執行時,該物件也會從 WeakSet 中刪除。這樣的話,如果你想要一種高效、輕便的解決方法去建立一組物件,就可以使用 WeakSet。垃圾回收的發生時間點取決於很多因素
Map
Map 和 WeakMap 在很多方面與 Set 和 WeakSet 相同,它們都有類似的屬性和方法。Map 和 Set 都是可迭代的
,這意味著我們可以迴圈遍歷它們
。而WeakMap 和 WeakSet 不會阻止物件被當作垃圾回收
。但是 Map 是唯一的,因為它們是鍵值對的集合
,而 Set 是唯一值的集合
。可以說 Set 類似於陣列
,而Map 類似於物件
。
建立和修改 Map
本質上,Map 是一個可以儲存鍵值對的物件,鍵和值
都可以是物件、原始值或二者的結合。
建立 Map
const employees = new Map();
console.log(employees); // Map {}
修改 Map
.set()
方法新增鍵值
const employees = new Map();
employees.set('james.parkes@udacity.com', {
firstName: 'James',
lastName: 'Parkes',
role: 'Content Developer'
});
employees.set('julia@udacity.com', {
firstName: 'Julia',
lastName: 'Van Cleve',
role: 'Content Developer'
});
employees.set('richard@udacity.com', {
firstName: 'Richard',
lastName: 'Kalehoff',
role: 'Content Developer'
});
console.log(employees);
/**
Map {'james.parkes@udacity.com' => Object {...}, 'julia@udacity.com' => Object {...}, 'richard@udacity.com' => Object {...}}
*/
.delete()
方法移除鍵值對
employees.delete('julia@udacity.com');
employees.delete('richard@udacity.com');
console.log(employees);
/**
Map {'james.parkes@udacity.com' => Object {firstName: 'James', lastName: 'Parkes', role: 'Course Developer'}}
*/
.clear()
方法從 Map 中刪除所有鍵值對
employees.clear()
console.log(employees); // Map {}
如果成功地刪除了鍵值對,.delete()
方法會返回 true
,失敗則返回 false
。.set()
如果成功執行,則返回 Map 物件本身
。如果你使用 .set()
向 Map 中新增鍵已存在的鍵值對
,不會收到錯誤,但是該鍵值對將覆蓋
Map 中的現有鍵值對。
處理 Map
.has()
方法並向其傳入一個鍵來檢查 Map 中是否存在該鍵值對
。
const members = new Map();
members.set('Evelyn', 75.68);
members.set('Liam', 20.16);
members.set('Sophia', 0);
members.set('Marcus', 10.25);
console.log(members.has('Xavier')); // false
console.log(members.has('Marcus')); // true
.get()
方法傳入一個鍵,檢索 Map 中的值。
console.log(members.get('Evelyn')); // 75.68
迴圈訪問 Map
三種方式迴圈訪問:
- 使用 Map 的預設迭代器迴圈訪問每個鍵或值
- 使用新的
for...of
迴圈來迴圈訪問每個鍵值對 - 使用 Map 的
.forEach()
方法迴圈訪問每個鍵值對
使用 MapIterator
在 Map 上使用 .keys()
和 .values()
方法將返回新的迭代器物件,叫做 MapIterator
。你可以將該迭代器物件儲存在新的變數中,並使用 .next()
迴圈訪問每個鍵或值。
// 訪問 Map 的鍵
let iteratorObjForKeys = members.keys();
iteratorObjForKeys.next();// Object {value: 'Evelyn', done: false}
iteratorObjForKeys.next(); // Object {value: 'Liam', done: false}
// 等等
// 訪問 Map 的值
let iteratorObjForValues = members.values();
iteratorObjForValues.next(); // Object {value: 75.68, done: false}
// 等等
使用 for…of 迴圈
for (const member of members) {
console.log(member);
}
// 鍵值對會拆分為一個陣列,第一個元素是鍵,第二個元素是值。
/**
['Evelyn', 75.68]
['Liam', 20.16]
['Sophia', 0]
['Marcus', 10.25]
*/
使用 forEach 迴圈
members.forEach((value, key) => console.log(value, key));
/**
'Evelyn' 75.68
'Liam' 20.16
'Sophia' 0
'Marcus' 10.25
*/
WeakMap
WeakMap 和普通 Map 很像,但是具有以下關鍵區別:
- WeakMap 只能包含
物件作為鍵
- WeakMap
無法迭代
,意味著無法迴圈訪問 - WeakMap 沒有
.clear()
方法
const book1 = { title: 'Pride and Prejudice', author: 'Jane Austen' };
const book2 = { title: 'The Catcher in the Rye', author: 'J.D. Salinger' };
const book3 = { title: 'Gulliver's Travels', author: 'Jonathan Swift' };
const library = new WeakMap();
library.set(book1, true);
library.set(book2, false);
library.set(book3, true);
console.log(library);
/**
WeakMap {Object {title: 'Pride and Prejudice', author: 'Jane Austen'} => true, Object {title: 'The Catcher in the Rye', author: 'J.D. Salinger'} => false, Object {title: 'Gulliver's Travels', author: 'Jonathan Swift'} => true}
*/
// 新增物件以外的內容作為鍵,系統將報錯!
library.set('The Grapes of Wrath', false);
// Uncaught TypeError: Invalid value used as weak map key(…)
垃圾回收
book1 = null;
console.log(library);
/**
WeakMap {Object {title: 'The Catcher in the Rye', author: 'J.D. Salinger'} => false, Object {title: 'Gulliver’s Travels', author: 'Jonathan Swift'} => true}
*/
Promise-非同步請求
簡介
使用 JavaScript Promise 是處理非同步請求
的一種新方法,是對我們過去程式碼構建方式的一種改良。處理一個請求需要來回等待,造成大量的停機時間,能夠在停機時間同時進行其它工作,並且通知我們請求處理完成,就是 Promise 在 JavaScript 中的作用。
使用 Promise
JavaScript Promise 是用新的 Promise 建構函式
- new Promise()
建立而成的。promise 使你能夠展開一些可以非同步完成的工作,並回到常規工作。建立 promise 時,必須向其提供非同步執行的程式碼
。將該程式碼當做引數提供給建構函式:
new Promise(function () {
window.setTimeout(function createSundae(flavor = 'chocolate') {
const sundae = {};
// 請求冰淇淋
// 得到錐形蛋筒
// 加熱冰淇淋
// 舀一大勺到蛋筒裡!
}, Math.random() * 2000);
});
JavaScript 如何通知我們它已經完成操作,準備好讓我們恢復工作?
它通過向初始函式中傳入兩個函式來實現這一點,通常我們將這兩個函式稱為 resolve
和 reject
。
resolve
函式-請求完成
new Promise(function (resolve, reject) {
window.setTimeout(function createSundae(flavor = 'chocolate') {
const sundae = {};
// 請求冰淇淋
// 得到錐形蛋筒
// 加熱冰淇淋
// 舀一大勺到蛋筒裡!
resolve(sundae);
}, Math.random() * 2000);
});
當 sundae 被成功建立後,它會呼叫 resolve
方法並向其傳遞我們要返回的資料,在本例中,返回的資料是完成的 sundae。因此 resolve
方法用來表示請求已完成,並且成功完成了請求。
reject
函式-請求失敗
如果請求存在問題,無法完成請求,那麼我們可以使用傳遞給該函式的第二個函式。通常,該函式儲存在一個叫做"reject
"的識別符號中,表示如果請求因為某種原因失敗了,應該使用該函式
。
new Promise(function (resolve, reject) {
window.setTimeout(function createSundae(flavor = 'chocolate') {
const sundae = {};
// 請求冰淇淋
// 得到錐形蛋筒
// 加熱冰淇淋
// 舀一大勺到蛋筒裡!
if ( /* iceCreamConeIsEmpty(flavor) */ ) {
reject(`Sorry, we're out of that flavor :-(`);
}
resolve(sundae);
}, Math.random() * 2000);
});
如果請求無法完成,則使用 reject
方法。注意,即使請求失敗了,我們依然可以返回資料。
Promise建構函式需要一個可以執行的函式,執行一段時間後,將成功完成(使用 resolve
方法)或失敗(使用 reject
方法)。當結果最終確定時(請求成功完成或失敗),現在 promise 已經實現了,並且將通知我們,這樣我們便能決定將如何對結果做處理。
Promise 立即返回物件
首先要注意的是,Promise 將立即返回一個物件。
const myPromiseObj = new Promise(function (resolve, reject) {
// 聖代建立程式碼
});
該物件上具有一個 .then()
方法,我們可以讓該方法通知我們 promise 中的請求成功與否。.then()
方法會接收兩個函式:
- 請求成功完成時要執行的函式
- 請求失敗時要執行的函式
mySundae.then(function(sundae) {
console.log(`Time to eat my delicious ${sundae}`);
}, function(msg) {
console.log(msg);
self.goCry(); // 不是一個真正的方法
});
傳遞給 .then()
的第一個函式將被呼叫,並傳入 Promise 的 resolve
函式需要使用的資料。這裡,該函式將接收 sundae
物件。第二個函式傳入的資料會在 Promise 的 reject
函式被呼叫時使用。
Proxy-代理
JavaScript 代理會讓一個物件代表另一個物件,來處理另一個物件的所有互動。代理可以直接處理請求,接收或傳送目標物件資料,以及處理一大堆其他的事情。
建立 Proxy
使用 Proxy 建構函式 new Proxy();
。Proxy 建構函式接收兩個專案:
- 它將要代理的物件
- 包含將為被代理物件處理的方法列表的物件
第二個物件叫做處理器。
建立 Proxy 的最簡單方式是提供物件和空的 handler(處理器)物件
。
var richard = {status: 'looking for work'};
var agent = new Proxy(richard, {});
agent.status; // 返回 'looking for work'
上述程式碼並沒有對 Proxy 執行任何特殊操作,只是將請求直接傳遞給源物件
!如果我們希望 Proxy 物件截獲請求,這就是 handler 物件的作用了!
讓 Proxy 變得有用的關鍵是當做第二個物件傳遞給 Proxy 建構函式的 handler 物件
。handler 物件由將用於訪問屬性的方法
構成。
Get Trap(捕獲器)
get
用來截獲對屬性的呼叫:
const richard = {status: 'looking for work'};
const handler = {
get(target, propName) {
console.log(target); // `richard` 物件,不是 `handler` 也不是 `agent`
console.log(propName); // 代理(本例中為`agent`)正在檢查的屬性名稱
}
};
const agent = new Proxy(richard, handler);
agent.status; // 登出 richard 物件(不是代理物件!)和正在訪問的屬性的名稱(`status`)
在上述程式碼中,handler
物件具有一個 get
方法(因為被用在 Proxy 中,所以將"function"(方法)稱之為"trap"(捕獲器))。當程式碼 agent.status;
在最後一行執行時,因為存在 get
捕獲器,它將截獲該呼叫以獲得 status
(狀態)屬性並執行 get
捕獲器方法。這樣將會輸出 Proxy 的目標物件(richard 物件),然後輸出被請求的屬性(status
屬性)的名稱。它的作用就是這些!它不會實際地輸出屬性
!這很重要 —— 如果使用了捕獲器,你需要確保為該捕獲器提供所有的功能
。
從 Proxy 內部訪問目標物件
如果我們想真正地提供真實的結果,我們需要返回目標物件的屬性:
const richard = {status: 'looking for work'};
const handler = {
get(target, propName) {
console.log(target);
console.log(propName);
return target[propName];
}
};
const agent = new Proxy(richard, handler);
agent.status; // (1)列印 richard 物件,(2)列印被訪問的屬性,(3)返回 richard.status 中的文字
在 get
trap 中新增了最後一行 return target[propName];
,這樣將會訪問目標物件的屬性
並返回它。
直接獲取 Proxy 的返回資訊
使用 Proxy 提供直接的反饋:
const richard = {status: 'looking for work'};
const handler = {
get(target, propName) {
return `He's following many leads, so you should offer a contract as soon as possible!`;
}
};
const agent = new Proxy(richard, handler);
agent.status; // 返回文字 `He's following many leads, so you should offer a contract as soon as possible!`
對於上述程式碼,Proxy 甚至不會檢查目標物件,直接對呼叫程式碼做出響應
。
因此每當 Proxy 上的屬性被訪問,get
trap 將接管任務。如果我們想截獲呼叫以更改屬性
,則需要使用 set
trap!
set
trap 用來截獲將更改屬性的程式碼。set trap 將接收: 它代理的物件 被設定的屬性 Proxy 的新值
const richard = {status: 'looking for work'};
const handler = {
set(target, propName, value) {
if (propName === 'payRate') { // 如果工資正在確定,則需要15%作為佣金。
value = value * 0.85;
}
target[propName] = value;
}
};
const agent = new Proxy(richard, handler);
agent.payRate = 1000; // 將演員的工資設定為 1,000美元
agent.payRate; // 850美元是演員的實際工資
在上述程式碼中,注意 set
trap 會檢查是否設定了 payRate
屬性。如果設定了,Proxy 就從中拿走 15% 的費用作為自己的佣金!當演員的薪酬是一千美元時,因為 payRate
屬性已設定,程式碼從中扣除 15% 的費用,並將實際 payRate
屬性設為 850;
其他 Trap
實際上總共有 13 種不同的 Trap,它們都可以用在處理程式中!
- get trap - 使 proxy 能處理對屬性訪問權的呼叫
- set trap - 使 proxy 能將屬性設為新值
- apply trap - 使 proxy 能被呼叫(被代理的物件是函式)
- has trap - 使 proxy 能使用 in 運算子
- deleteProperty trap - 使 proxy 能確定屬性是否被刪除
- ownKeys trap - 使 proxy 能處理當所有鍵被請求時的情況
- construct trap - 使 proxy 能處理 proxy 與 new 關鍵字一起使用當做建構函式的情形
- defineProperty trap - 使 proxy 能處理當 defineProperty 被用於建立新的物件屬性的情形
- getOwnPropertyDescriptor trap - 使 proxy 能獲得屬性的描述符
- preventExtenions trap - 使 proxy 能對 proxy 物件呼叫 Object.preventExtensions()
- isExtensible trap - 使 proxy 能對 proxy 物件呼叫 Object.isExtensible
- getPrototypeOf trap - 使 proxy 能對 proxy 物件呼叫 Object.getPrototypeOf
- setPrototypeOf trap - 使 proxy 能對 proxy 物件呼叫 Object.setPrototypeOf
Proxy 與 ES5 Getter/Setter
對於 ES5 的 getter 和 setter 方法,你需要提前知道要獲取/設定的屬性
:
var obj = {
_age: 5,
_height: 4,
get age() {
console.log(`getting the "age" property`);
console.log(this._age);
},
get height() {
console.log(`getting the "height" property`);
console.log(this._height);
}
};
對於上述程式碼,注意在初始化物件時,我們需要設定 get age()
和 get height()
。因此,當我們呼叫下面的程式碼時,將獲得以下結果:
obj.age; // 列印 'getting the "age" property' 和 5
obj.height; // 列印 'getting the "height" property' 和 4
但是當我們向該物件新增新的屬性時,並沒有顯示 age
和 height
屬性那樣生成的 getting the "weight" property
訊息。
obj.weight = 120; // 在物件上設定一個新的屬性
obj.weight; // 只列印120
對於 ES6 中的 Proxy,我們不需要提前知道這些屬性:
const proxyObj = new Proxy({age: 5, height: 4}, {
get(targetObj, property) {
console.log(`getting the ${property} property`);
console.log(targetObj[property]);
}
});
proxyObj.age; // 列印 'getting the age property' 和 5
proxyObj.height; // 列印 'getting the height property' 和 4
當我們新增新的屬性時
proxyObj.weight = 120; // 在物件上設定一個新的屬性
proxyObj.weight; // 列印 'getting the weight property' 和 120
因此 proxy 物件的某些功能可能看起來類似於現有的 ES5 getter/setter 方法,但是對於 proxy,在初始化物件時,不需要針對每個屬性使用 getter/setter 初始化物件
。
Proxy 是一種強大的建立和管理物件之間的互動的新方式。
生成器
每當函式被呼叫時,JavaScript 引擎就會在函式頂部啟動,並執行每行程式碼,直到到達底部。無法中途停止執行程式碼,並稍後重新開始。一直都是這種“執行到結束”的工作方式:
function getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log(name);
}
console.log('the function has ended');
}
getEmployee();
執行上述程式碼將在控制檯中輸出以下內容:
the function has started
Amanda
Diego
Farrin
James
Kagure
Kavita
Orit
Richard
the function has ended
如果你想先輸出前三名員工的姓名,然後停止一段時間,稍後再從停下的地方繼續輸出更多員工的姓名呢?普通函式無法這麼做,因為無法中途“暫停”執行函式。
可暫停的函式
如果我們希望能夠中途暫停執行函式,則需要使用 ES6 中新提供的一種函式,叫做 generator
(生成器)函式!
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log( name );
}
console.log('the function has ended');
}
星號表示該函式實際上是生成器!嘗試執行該函式
getEmployee();
// 這是我在 Chrome 中獲得的迴應:
getEmployee {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
注意,生成器的星號實際上可以放在 function
關鍵字和函式名稱之間的任何位置
。
生成器和迭代器
生成器被呼叫時,它不會執行函式中的任何程式碼,而是建立和返回迭代器。該迭代器可以用來執行實際生成器的內部程式碼。
const generatorIterator = getEmployee();
generatorIterator.next();
關鍵字 yield
關鍵字 yield
是 ES6 中新出現的關鍵字。只能用在生成器函式中。yield
會導致生成器暫停下來。我們向我們的生成器中新增 yield
:
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log(name);
yield;
}
console.log('the function has ended');
}
注意,現在 for...of
迴圈中出現了 yield
。如果我們呼叫該生成器(生成迭代器),然後呼叫 .next()
,將獲得以下輸出:
const generatorIterator = getEmployee();
generatorIterator.next();
/**
the function has started
Amanda
*/
generatorIterator.next(); // Diego
generatorIterator.next(); // Farrin
// ...
它能完全記住上次停下的地方!它獲取到陣列中的下一項(Diego),記錄它,然後再次觸發了 yield
,再次暫停。
向外面的世界生成資料
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
yield name;
}
console.log('the function has ended');
}
注意,現在從 console.log(name);
切換成了 yield name;
。做出這一更改後,當生成器執行時,它會把姓名從函式裡返回出去,然後暫停執行程式碼。我們看看具體效果:
const generatorIterator = getEmployee();
let result = generatorIterator.next();
result.value // 是 "Amanda"
generatorIterator.next().value // 是 "Diego"
generatorIterator.next().value // 是 "Farrin"
示例
迭代器的 .next()
方法需要被呼叫多少次,才能完全完成/用盡下面的 udacity 生成器函式:
function* udacity() {
yield 'Richard';
yield 'James'
}
它被呼叫的次數將比生成器函式中的 yield
表示式的數量多一次。
- 對
.next()
的第一次呼叫將啟動該函式,並執行為第一個yield
。 - 對
.next()
的第二次呼叫將從暫停的地方繼續,並執行第二個yield
。 .next()
的第三次(即最後一次)呼叫將再次從暫停的地方繼續,並執行到函式結尾處。
向生成器中傳送資料或從中向外傳送資料
我們可以使用關鍵字 yield
從生成器中獲取資料。我們還可以將資料傳送回生成器中。方式是使用 .next()
方法:
function* displayResponse() {
const response = yield;
console.log(`Your response is "${response}"!`);
}
const iterator = displayResponse();
iterator.next(); // 開始執行生成器函式
iterator.next('Hello Udacity Student'); // 將資料傳送到生成器中
// 上面的一行列印到控制檯:你的響應是 "Hello Udacity Student"!
使用資料呼叫 .next()
(即 .next('Richard')
)會將該資料傳送到生成器函式中上次離開的地方。它會將 yield
關鍵字替換為你提供的資料。
關鍵字 yield
用來暫停生成器並向生成器外傳送資料,然後 .next()
方法用來向生成器中傳入資料。下面是使用這兩種過程來一次次地迴圈訪問姓名列表的示例:
function* getEmployee() {
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
const facts = [];
for (const name of names) {
// yield *出* 每個名稱並將返回的資料儲存到 facts 陣列中
facts.push(yield name);
}
return facts;
}
const generatorIterator = getEmployee();
// 從生成器中獲取第一個名稱
let name = generatorIterator.next().value;
// 將資料傳入 *並* 獲取下一個名稱
name = generatorIterator.next(`${name} is cool!`).value;
// 將資料傳入 *並* 獲取下一個名稱
name = generatorIterator.next(`${name} is awesome!`).value;
// 將資料傳入 *並* 獲取下一個名稱
name = generatorIterator.next(`${name} is stupendous!`).value;
// 你懂的
name = generatorIterator.next(`${name} is rad!`).value;
name = generatorIterator.next(`${name} is impressive!`).value;
name = generatorIterator.next(`${name} is stunning!`).value;
name = generatorIterator.next(`${name} is awe-inspiring!`).value;
// 傳遞最後一個資料,生成器結束並返回陣列
const positions = generatorIterator.next(`${name} is magnificent!`).value;
// 在自己的行上顯示每個名稱及其描述
positions.join('\n');
示例
function* createSundae() {
const toppings = [];
toppings.push(yield);
toppings.push(yield);
toppings.push(yield);
return toppings;
}
var it = createSundae();
it.next('hot fudge');
it.next('sprinkles');
it.next('whipped cream');
it.next();
注意,第一次呼叫 .next()
將初始化生成器,將在第一個 yield
位置暫停。第二次呼叫 .next()
將向該 yield
提供資料。
數數有多少個 yield
,以及每次呼叫 .next()
時,資料是如何被傳入的。
toppings
陣列的最後一項將是 undefined
。因為:
- 因為第一次呼叫
.next()
傳入了一些資料,但是該資料沒有儲存在任何位置。 - 最後一次呼叫
.next()
應該會獲得一些資料(空資料),因為生成到對toppings.push()
的最後一次呼叫中。
生成器是強大的新型函式,能夠暫停執行程式碼,同時保持自己的狀態。生成器適用於一次一個地迴圈訪問列表項,以便單獨處理每項,然後再轉到下一項。還可以使用迭代器來處理巢狀回撥
。例如,假設某個函式需要獲得所有倉庫的列表和被加星標的次數。在獲得每個倉庫的星標數量之前,需要獲得使用者的資訊。獲得使用者的個人資料後,程式碼可以利用該資訊查詢所有的倉庫。
相關文章
- 前端進階-ES6函式前端函式
- ES6常用總結 (前端開發js技術進階提升)前端JS
- 高階前端進階(七)前端
- 高階前端進階(五)前端
- 高階前端進階(三)前端
- [ ES6 ] 進階篇(一) —— PromisePromise
- 高階前端進階系列 - webview前端WebView
- 【進階系列】前端開發環境構建(一)CSS -- Sass前端開發環境CSS
- 前端進階-樣式前端
- 前端進階之困前端
- 前端內功修煉:5大主流佈局系統進階,系統進階佈局技術前端
- 前端進階 -- TS相關前端
- 前端進階課程之this指向前端
- 前端進階-類和物件前端物件
- 前端進階的破冰之旅前端
- 高階前端的進階——CSS之flex前端CSSFlex
- 前端進階(1)Web前端效能優化前端Web優化
- python高階內建函式Python函式
- 前端基礎之jQuery進階前端jQuery
- web前端進階篇(一 )JSWeb前端JS
- 前端進階知識彙總前端
- 前端進階課程之宣告提升前端
- 前端進階-深入瞭解物件前端物件
- 詳解前端進階指南教程前端
- 前端工程師的進階之路前端工程師
- 前端進階(一)掌握Web API,開發常見的頁面互動功能前端WebAPI
- 前端高階進階:CICD 下前端的多特性分支環境部署前端
- 使用 Mac 內建的螢幕共享功能進行遠端桌面協助Mac
- Conflux 內建合約功能介紹UX
- 聊聊wireshark的進階使用功能
- 2-django進階之日誌功能Django
- 進階|教你使用自定義屬性功能管理 Word 文件中的待定內容
- 前端進階-執行時函式前端函式
- Netty進階內部元件詳解Netty元件
- 程式猿進階之路“內網域名”內網
- SAP PLM 進階 2 – 主要核心功能
- [廣州][內推]網易高階前端崗招人內推前端
- Kotlin進階(二)中綴、內聯、高階函式Kotlin函式