ES6語法筆記(Symbol、Set、Map、Proxy、Reflect、Promise)
**以下內容均摘自ECMAScript 6 入門——阮一峰
一、Symbol數據類型(undefined
、null
、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)、Symbol)
1.Symbol()表示一個獨一無二的值,接受一個參數作為該值的描述。
// 沒有參數的情況 let s1 = Symbol(); let s2 = Symbol(); s1 === s2 // false // 有參數的情況 let s1 = Symbol(‘foo‘); let s2 = Symbol(‘foo‘); s1 === s2 // false
Symbol值不能與其他類型的值計算 Symbol值可以轉化為布爾值(true)
2.定義獨一無二的屬性名,防止被改寫或覆蓋
let mySymbol = Symbol(); // 第一種寫法 let a = {}; a[mySymbol] = ‘Hello!‘; // 第二種寫法 let a = { [mySymbol]: ‘Hello!‘ };
3.Symbol的遍歷
Object.getOwnPropertySymbols
方法,可以獲取指定對象的所有 Symbol 屬性名
Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()、
for...in
、for...of均無法遍歷到Symbol類型屬性
4.使用Symbol.for()重新使用同一個 Symbol 值(沒有則新建並返回一個以該字符串為名稱的 Symbol 值)
Symbol.for("bar") === Symbol.for("bar") // true Symbol("bar") === Symbol("bar") // false
5.Symbol.keyFor()方法返回一個已登記的 Symbol 類型值的key
。
let s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" let s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
二、Set數據結構類似於數組、但是成員的值都是唯一的,沒有重復的值。
const set = new Set([1, 2, 3, 4, 4]); [...set]// [1, 2, 3, 4]
1.Set實例的屬性和方法
Set.prototype.constructor
:構造函數,默認就是Set
函數。Set.prototype.size
:返回Set
實例的成員總數。add(value)
:添加某個值,返回 Set 結構本身。delete(value)
:刪除某個值,返回一個布爾值,表示刪除是否成功。has(value)
:返回一個布爾值,表示該值是否為Set
的成員。clear()
:清除所有成員,沒有返回值。
Array.from
方法可以將 Set 結構轉為數組。
const items = new Set([1, 2, 3, 4, 5]); const array = Array.from(items);
2.Set結構的遍歷方法
keys()
:返回鍵名的遍歷器values()
:返回鍵值的遍歷器entries()
:返回鍵值對的遍歷器forEach()
:使用回調函數遍歷每個成員let set = new Set([‘red‘, ‘green‘, ‘blue‘]); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"]
在遍歷操作中,同步改變原來的 Set 結構
// 方法一 let set = new Set([1, 2, 3]); set = new Set([...set].map(val => val * 2)); // set的值是2, 4, 6 // 方法二 let set = new Set([1, 2, 3]); set = new Set(Array.from(set, val => val * 2)); // set的值是2, 4, 6
3.WeakSet 結構與 Set 類似、無法遍歷、只能存放對象、且對象為弱引用(如果其他對象都不再引用該對象,那麽垃圾回收機制會自動回收該對象所占用的內存,不考慮該對象還存在於 WeakSet 之中)
三、Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。
const m = new Map(); const o = {p: ‘Hello World‘}; m.set(o, ‘content‘) m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
const map = new Map([ [‘name‘, ‘張三‘], [‘title‘, ‘Author‘] ]); map.size // 2 map.has(‘name‘) // true map.get(‘name‘) // "張三" map.has(‘title‘) // true map.get(‘title‘) // "Author"
1.Map結構的屬性和操作方法
- size 屬性
- set(key, value)
- get(key)
- has(key)
- delete(key)
- clear()
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。entries()
:返回所有成員的遍歷器。forEach()
:遍歷 Map 的所有成員。
2.與其他數據結構的相互轉化
//Map 轉為數組 const myMap = new Map() .set(true, 7) .set({foo: 3}, [‘abc‘]); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ ‘abc‘ ] ] ] //數組 轉為 Map new Map([ [true, 7], [{foo: 3}, [‘abc‘]] ]) // Map { // true => 7, // Object {foo: 3} => [‘abc‘] // } //Map 轉為對象 //如果所有 Map 的鍵都是字符串,它可以無損地轉為對象。 //Map 轉為 JSON //Map 的鍵名都是字符串,這時可以選擇轉為對象 JSON。 //Map 的鍵名有非字符串,這時可以選擇轉為數組 JSON。 //JSON 轉為 Map同理
3.WeakMap
只接受對象作為鍵名(null
除外),不接受其他類型的值作為鍵名。鍵名所引用的對象都是弱引用。沒有遍歷操作(即沒有keys()
、values()
和entries()
方法),也沒有size
屬性。無法清空,即不支持clear
方法。因此,WeakMap
只有四個方法可用:get()
、set()
、has()
、delete()
。
四、Proxy
Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
Proxy 實際上重載(overload)了點運算符,即用自己的定義覆蓋了語言的原始定義。
//ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。 var proxy = new Proxy(target, handler);
1.Proxy 支持的攔截操作
get(target, propKey, receiver):攔截對象屬性的讀取,比如proxy.foo
和proxy[‘foo‘]
。
var person = { name: "張三" }; var proxy = new Proxy(person, { get: function(target, property) { if (property in target) { return target[property]; } else { throw new ReferenceError("Property \"" + property + "\" does not exist."); } } }); proxy.name // "張三" proxy.age // 拋出一個錯誤
set(target, propKey, value, receiver):攔截對象屬性的設置,比如proxy.foo = v
或proxy[‘foo‘] = v
,返回一個布爾值。
let validator = { set: function(obj, prop, value) { if (prop === ‘age‘) { if (!Number.isInteger(value)) { throw new TypeError(‘The age is not an integer‘); } if (value > 200) { throw new RangeError(‘The age seems invalid‘); } } // 對於滿足條件的 age 屬性以及其他屬性,直接保存 obj[prop] = value; } }; let person = new Proxy({}, validator); person.age = 100; person.age // 100 person.age = ‘young‘ // 報錯 person.age = 300 // 報錯
apply(target, object, args):攔截 Proxy 實例作為函數調用的操作
var twice = { apply (target, ctx, args) { return Reflect.apply(...arguments) * 2; } }; function sum (left, right) { return left + right; }; var proxy = new Proxy(sum, twice); proxy(1, 2) // 6 proxy.call(null, 5, 6) // 22 proxy.apply(null, [7, 8]) // 30
has(target, propKey):攔截propKey in proxy
的操作,返回一個布爾值。
var handler = { has (target, key) { if (key[0] === ‘_‘) { return false; } return key in target; } }; var target = { _prop: ‘foo‘, prop: ‘foo‘ }; var proxy = new Proxy(target, handler); ‘_prop‘ in proxy // false
construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如new proxy(...args)
。
var p = new Proxy(function () {}, { construct: function(target, args) { console.log(‘called: ‘ + args.join(‘, ‘)); return { value: args[0] * 10 }; } }); (new p(1)).value // "called: 1" // 10
deleteProperty(target, propKey):攔截delete proxy[propKey]
的操作,返回一個布爾值。
ownKeys(target):攔截Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys()
的返回結果僅包括目標對象自身的可遍歷屬性。、
getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回屬性的描述對象。
defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一個布爾值。
preventExtensions(target):攔截Object.preventExtensions(proxy)
,返回一個布爾值。
getPrototypeOf(target):攔截Object.getPrototypeOf(proxy)
,返回一個對象。
isExtensible(target):攔截Object.isExtensible(proxy)
,返回一個布爾值。
setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto)
,返回一個布爾值。如果目標對象是函數,那麽還有兩種額外操作可以攔截。
2.this指向問題
在 Proxy 代理的情況下,目標對象內部的this
關鍵字會指向 Proxy 代理。
const target = { m: function () { console.log(this === proxy); } }; const handler = {}; const proxy = new Proxy(target, handler); target.m() // false proxy.m() // true //this綁定原始對象,就可以解決這個問題。 const target = new Date(‘2015-01-01‘); const handler = { get(target, prop) { if (prop === ‘getDate‘) { return target.getDate.bind(target); } return Reflect.get(target, prop); } }; const proxy = new Proxy(target, handler); proxy.getDate() // 1
五、Reflect
將Object
對象的一些明顯屬於語言內部的方法(比如Object.defineProperty
),放到Reflect
對象上。
修改某些Object
方法的返回結果,讓其變得更合理。
讓Object
操作都變成函數行為。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
讓它們變成了函數行為。
Reflect
對象的方法與Proxy
對象的方法一一對應,只要是Proxy
對象的方法,就能在Reflect
對象上找到對應的方法。也就是說,不管Proxy
怎麽修改默認行為,你總可以在Reflect
上獲取默認行為。以下是Reflect對象的13個靜態方法。
Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, } Reflect.get(myObject, ‘foo‘) // 1 Reflect.get(myObject, ‘bar‘) // 2 Reflect.get(myObject, ‘baz‘) // 3 //如果name屬性部署了讀取函數(getter),則讀取函數的this綁定receiver。 var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, }; var myReceiverObject = { foo: 4, bar: 4, }; Reflect.get(myObject, ‘baz‘, myReceiverObject) // 8
//Reflect.ownKeys方法用於返回對象的所有屬性,基本等同於Object.getOwnPropertyNames與Object.getOwnPropertySymbols之和。 var myObject = { foo: 1, bar: 2, [Symbol.for(‘baz‘)]: 3, [Symbol.for(‘bing‘)]: 4, }; // 舊寫法 Object.getOwnPropertyNames(myObject) // [‘foo‘, ‘bar‘] Object.getOwnPropertySymbols(myObject) //[Symbol(baz), Symbol(bing)] // 新寫法 Reflect.ownKeys(myObject) // [‘foo‘, ‘bar‘, Symbol(baz), Symbol(bing)]
六、Promise
1.Promise 是異步編程的一種解決方案。Promise 是一個容器,裏面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。
它的缺點包括無法取消Promise
,一旦新建它就會立即執行,無法中途取消。如果不設置回調函數,Promise
內部拋出的錯誤,不會反應到外部。當處於pending
狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
Promise
構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve
和reject
。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。
Promise
實例生成以後,可以用then
方法分別指定resolved
狀態和rejected
狀態的回調函數。
//回調錯誤函數為可選項 promise.then(function(value) { // success }, function(error) { // failure });
//Promise 新建後就會立即執行。 //then方法指定的回調函數,將在當前腳本所有同步任務執行完才會執行,所以resolved最後輸出。 let promise = new Promise(function(resolve, reject) { console.log(‘Promise‘); resolve(); }); promise.then(function() { console.log(‘resolved.‘); }); console.log(‘Hi!‘); // Promise // Hi! // resolved
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); } 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("/posts.json").then(function(json) { console.log(‘Contents: ‘ + json); }, function(error) { console.error(‘出錯了‘, error); });
resolve
函數的參數除了正常的值以外,還可能是另一個 Promise 實例
const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error(‘fail‘)), 3000) }) const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2 .then(result => console.log(result)) .catch(error => console.log(error)) // Error: fail
由於p2
返回的是另一個 Promise,導致p2
自己的狀態無效了,由p1
的狀態決定p2
的狀態。
2.Promise.prototype.then()方法返回的是一個新的Promise
實例(註意,不是原來那個Promise
實例)。因此可以采用鏈式寫法,即then
方法後面再調用另一個then
方法。
//第一個回調函數完成以後,會將返回結果作為參數,傳入第二個回調函數。 getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
3.Promise.prototype.catch()用於指定發生錯誤時的回調函數。如果異步操作拋出錯誤,狀態就會變為rejected
,就會調用catch
方法指定的回調函數,處理這個錯誤。另外,then
方法指定的回調函數,如果運行中拋出錯誤,也會被catch
方法捕獲。
Promise 對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch
語句捕獲。
//第二種寫法要好於第一種寫法,理由是第二種寫法可以捕獲前面then方法執行中的錯誤 // bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
如果沒有使用catch
方法指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。
4.Promise.prototype.finally()方法用於指定不管 Promise 對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。
5.Promise.all()方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
//p1、p2、p3都是 Promise 實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉為 Promise 實例,再進一步處理。 const p = Promise.all([p1, p2, p3]);
只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態才會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。
// 生成一個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){ // ... });
如果作為參數的 Promise 實例,自己定義了catch
方法,那麽它一旦被rejected
,並不會觸發Promise.all()
的catch
方法。調動catch方法後實例會resolved
,因此會調用all的
then
方法指定的回調函數,而不會調用catch
方法指定的回調函數。如果實例沒有catch方法,則會調用all的catch方法。
6.Promise.race()同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
//只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。 const p = Promise.race([p1, p2, p3]);
7.Promise.resolve()將現有對象轉為 Promise 對象
如果參數是 Promise 實例,那麽Promise.resolve
將不做任何修改、原封不動地返回這個實例。
如果參數是 thenable對象(指的是具有then
方法的對象),Promise.resolve
方法會將這個對象轉為 Promise 對象,然後就立即執行thenable
對象的then
方法。
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });
如果參數是一個原始值,或者是一個不具有then
方法的對象,則Promise.resolve
方法返回一個新的 Promise 對象,狀態為resolved
。
const p = Promise.resolve(‘Hello‘); p.then(function (s){ console.log(s) }); // Hello
如果調用時不帶參數,直接返回一個resolved
狀態的 Promise 對象。
//setTimeout(fn, 0)在下一輪“事件循環”開始時執行,Promise.resolve()在本輪“事件循環”結束時執行,console.log(‘one‘)則是立即執行,因此最先輸出。 setTimeout(function () { console.log(‘three‘); }, 0); Promise.resolve().then(function () { console.log(‘two‘); }); console.log(‘one‘); // one // two // three
8.Promise.reject()方法也會返回一個新的 Promise 實例,該實例的狀態為rejected
。
Promise.reject()
方法的參數,會原封不動地作為reject
的理由,變成後續方法的參數。這一點與Promise.resolve
方法不一致。
ES6語法筆記(Symbol、Set、Map、Proxy、Reflect、Promise)