ES6新紀(jì)元上

一、Symbol

  1. 為啥需要Symbol?
    ES5里面對象的屬性名都是字符串,如果你需要使用一個(gè)別人提供的對象,你對這個(gè)對象有哪些屬性也不是很清楚,但又想為這個(gè)對象新增一些屬性,那么你新增的屬性名就很可能和原來的屬性名發(fā)送沖突,顯然我們是不希望這種情況發(fā)生的。所以,我們需要確保每個(gè)屬性名都是獨(dú)一無二的,這樣就可以防止屬性名的沖突了。因此,ES6里就引入了Symbol,用它來產(chǎn)生一個(gè)獨(dú)一無二的值。
  2. Symbol是什么?
    Symbol實(shí)際上是ES6引入的一種原始數(shù)據(jù)類型,除了Symbol,JavaScript還有其他6種數(shù)據(jù)類型,分別是Undefined、Null、Boolean、String、Number、對象,這6種數(shù)據(jù)類型都是ES5中就有的。
  3. 怎么生成一個(gè)Symbol類型的值?
    Symbol值是通過Symbol函數(shù)生成的。
let s = Symbol();
console.log(s);  // Symbol()
typeof s;  // "symbol"
  1. Symbol函數(shù)前不能用new。
    Symbol函數(shù)不是一個(gè)構(gòu)造函數(shù),前面不能用new操作符。所以Symbol類型的值也不是一個(gè)對象,不能添加任何屬性,它只是一個(gè)類似于字符型的數(shù)據(jù)類型。如果強(qiáng)行在Symbol函數(shù)前加上new操作符,會(huì)報(bào)錯(cuò),如下:
let s = new Symbol();
// Uncaught TypeError: Symbol is not a constructor(…)
  1. Symbol函數(shù)的參數(shù)。
  • 字符串作為參數(shù)。
    用上面的方法生成的Symbol值不好進(jìn)行區(qū)分,Symbol函數(shù)還可以接受一個(gè)字符串參數(shù),來對產(chǎn)生的Symbol值進(jìn)行描述,方便我們區(qū)分不同的Symbol值。
let s1 = Symbol('s1');
let s2 = Symbol('s2');
console.log(s1);  // Symbol(s1)
console.log(s2);  // Symbol(s2)
s1 === s2;  //  false
let s3 = Symbol('s2');
s2 === s3;  //  false

給Symbol函數(shù)加了參數(shù)之后,控制臺輸出的時(shí)候可以區(qū)分到底是哪一個(gè)值。Symbol函數(shù)的參數(shù)只是對當(dāng)前Symbol值的描述,因此相同參數(shù)的Symbol函數(shù)返回值是不相等的。

  • 對象作為參數(shù)。
    如果Symbol函數(shù)的參數(shù)是一個(gè)對象,就會(huì)調(diào)用該對象的toString方法,將其轉(zhuǎn)化為一個(gè)字符串,然后才生成一個(gè)Symbol值。所以,說到底,Symbol函數(shù)的參數(shù)只能是字符串。
  • Symbol值不可以進(jìn)行運(yùn)算。
    既然Symbol是一種數(shù)據(jù)類型,那我們一定想知道Symbol值是否能進(jìn)行運(yùn)算。告訴你,Symbol值是不能進(jìn)行運(yùn)算的,不僅不能和Symbol值進(jìn)行運(yùn)算,也不能和其他類型的值進(jìn)行運(yùn)算,否則會(huì)報(bào)錯(cuò)。
    Symbol值可以顯式轉(zhuǎn)化為字符串和布爾值,但是不能轉(zhuǎn)為數(shù)值。
var mysym1 = Symbol('my symbol');
mysym1.toString() //  'Symbol('my symbol')'
String(mysym1)  //  'Symbol('my symbol')'

var mysym2 = Symbol();
Boolean(mysym2);  // true
Number(mysym2)  // TypeError: Cannot convert a Symbol value to a number(…)
  • Symbol作屬性名。
    Symbol就是為對象的屬性名而生,那么Symbol值怎么作為對象的屬性名呢?有下面幾種寫法:
let a = {};
let s4 = Symbol();
// 第一種寫法
a[s4] = 'mySymbol';
// 第二種寫法
a = {
    [s4]: 'mySymbol'
}
// 第三種寫法
Object.defineProperty(a, s4, {value: 'mySymbol1'});
a.s4;  //  undefined
a.s4 = 'mySymbol2';
a[s4]  //  mySymbol1
a['s4']  // 'mySymbol2'

使用對象的Symbol值作為屬性名時(shí),獲取相應(yīng)的屬性值不能用點(diǎn)運(yùn)算符;
如果用點(diǎn)運(yùn)算符來給對象的屬性賦Symbol類型的值,實(shí)際上屬性名會(huì)變成一個(gè)字符串,而不是一個(gè)Symbol值;
在對象內(nèi)部,使用Symbol值定義屬性時(shí),Symbol值必須放在方括號之中,否則只是一個(gè)字符串。

  1. Symbol值作為屬性名的遍歷。
    使用for...in和for...of都無法遍歷到Symbol值的屬性,Symbol值作為對象的屬性名,也無法通過Object.keys()、Object.getOwnPropertyNames()來獲取了。但是,不同擔(dān)心,這種平常的需求肯定是會(huì)有解決辦法的。我們可以使用Object.getOwnPropertySymbols()方法獲取一個(gè)對象上的Symbol屬性名。也可以使用Reflect.ownKeys()返回所有類型的屬性名,包括常規(guī)屬性名和 Symbol屬性名。
let s5 = Symbol('s5');
let s6 = Symbol('s6');
let a = {
    [s5]: 's5',
    [s6]: 's6'
}
Object.getOwnPropertySymbols(a);   // [Symbol(s5), Symbol(s6)]
a.hello = 'hello';
Reflect.ownKeys(a);  //  ["hello", Symbol(s5), Symbol(s6)]

利用Symbol值作為對象屬性的名稱時(shí),不會(huì)被常規(guī)方法遍歷到這一特性,可以為對象定義一些非私有的但是又希望只有內(nèi)部可用的方法。

  1. Symbol.for()和Symbol.keyFor()。
    Symbol.for()函數(shù)也可以用來生成Symbol值,但該函數(shù)有一個(gè)特殊的用處,就是可以重復(fù)使用一個(gè)Symbol值。
let s1 = Symbol.for("s11");
let s2 = Symbol.for("s22");

console.log(s1===s2)//false

let s3 = Symbol("s33");
let s4 = Symbol("s33");

console.log(s3===s4)//false

console.log(Symbol.keyFor(s3))//undefined
console.log(Symbol.keyFor(s2))//"s22"
console.log(Symbol.keyFor(s1))//"s11"

Symbol.for()函數(shù)要接受一個(gè)字符串作為參數(shù),先搜索有沒有以該參數(shù)作為名稱的Symbol值,如果有,就直接返回這個(gè)Symbol值,否則就新建并返回一個(gè)以該字符串為名稱的Symbol值。
Symbol.keyFor()函數(shù)是用來查找一個(gè)Symbol值的登記信息的,Symbol()寫法沒有登記機(jī)制,所以返回undefined;而Symbol.for()函數(shù)會(huì)將生成的Symbol值登記在全局環(huán)境中,所以Symbol.keyFor()函數(shù)可以查找到用Symbol.for()函數(shù)生成的Symbol值。

  1. 內(nèi)置Symbol值。
    ES6提供了11個(gè)內(nèi)置的Symbol值,分別是Symbol.hasInstance 、Symbol.isConcatSpreadable 、Symbol.species 、Symbol.match 、Symbol.replace 、Symbol.search 、Symbol.split 、Symbol.iterator 、Symbol.toPrimitive 、Symbol.toStringTag 、Symbol.unscopables 等。

二、Set和Map數(shù)據(jù)結(jié)構(gòu)

  1. Set
    es6提供了新的數(shù)據(jù)結(jié)構(gòu)Set,它類似于數(shù)組,但是成員的值都是唯一的,沒有重復(fù)的值。Set本身是一個(gè)構(gòu)造函數(shù),用來生成Set數(shù)據(jù)結(jié)構(gòu)。
const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4
//通過add方法向 Set 結(jié)構(gòu)加入成員,結(jié)果表明 Set 結(jié)構(gòu)不會(huì)添加重復(fù)的值。

Set 函數(shù)可以接受一個(gè)數(shù)組作為參數(shù),用來初始化。

// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 例三
function divs () {
  return [...document.querySelectorAll('div')];
}

const set = new Set(divs());
set.size // 56

// 類似于
divs().forEach(div => set.add(div));
set.size // 56

利用Set數(shù)據(jù)結(jié)構(gòu)的成員都是唯一的這個(gè)特性,可以輕松對數(shù)組去重。

// 去除數(shù)組的重復(fù)成員
[...new Set(array)]

向 Set 加入值的時(shí)候,不會(huì)發(fā)生類型轉(zhuǎn)換,所以5"5"是兩個(gè)不同的值。
Set 內(nèi)部判斷兩個(gè)值是否不同,使用的算法叫做“Same-value equality”,它類似于精確相等運(yùn)算符(===),主要的區(qū)別是NaN等于自身,而精確相等運(yùn)算符認(rèn)為NaN不等于自身。
兩個(gè)對象總是不相等的。
Set 結(jié)構(gòu)的實(shí)例有以下屬性:

  • Set.prototype.constructor:構(gòu)造函數(shù),默認(rèn)就是Set函數(shù)。
  • Set.prototype.size:返回Set實(shí)例的成員總數(shù)。
    Set 實(shí)例的方法分為兩大類:操作方法(用于操作數(shù)據(jù))和遍歷方法(用于遍歷成員)。

四個(gè)操作方法:

  • add(value):添加某個(gè)值,返回 Set 結(jié)構(gòu)本身。
  • delete(value):刪除某個(gè)值,返回一個(gè)布爾值,表示刪除是否成功。
  • has(value):返回一個(gè)布爾值,表示該值是否為Set的成員。
  • clear():清除所有成員,沒有返回值。
    Array.from()可以將 Set 結(jié)構(gòu)轉(zhuǎn)為數(shù)組。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

Set 結(jié)構(gòu)的實(shí)例有四個(gè)遍歷方法,可以用于遍歷成員。

  • keys():返回鍵名的遍歷器
  • values():返回鍵值的遍歷器
  • entries():返回鍵值對的遍歷器
  • forEach():使用回調(diào)函數(shù)遍歷每個(gè)成員
    keys方法、values方法、entries方法返回的都是遍歷器對象。由于 Set 結(jié)構(gòu)沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個(gè)值),所以keys方法和values方法的行為完全一致。
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 結(jié)構(gòu)的實(shí)例默認(rèn)可遍歷,它的默認(rèn)遍歷器生成函數(shù)就是它的values方法。
Set 結(jié)構(gòu)的實(shí)例與數(shù)組一樣,也擁有forEach方法,用于對每個(gè)成員執(zhí)行某種操作,沒有返回值。

let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

forEach方法還可以有第二個(gè)參數(shù),表示綁定處理函數(shù)內(nèi)部的this對象。

  1. WeakSet
    WeakSet 結(jié)構(gòu)與 Set 類似,也是不重復(fù)的值的集合。但是,它與 Set 有兩個(gè)區(qū)別:
  • WeakSet 的成員只能是對象,而不能是其他類型的值。
  • WeakSet 中的對象都是弱引用,即垃圾回收機(jī)制不考慮 WeakSet 對該對象的引用,也就是說,如果其他對象都不再引用該對象,那么垃圾回收機(jī)制會(huì)自動(dòng)回收該對象所占用的內(nèi)存,不考慮該對象還存在于 WeakSet 之中。
    由于上面這個(gè)特點(diǎn),WeakSet 的成員是不適合引用的,因?yàn)樗鼤?huì)隨時(shí)消失。另外,由于 WeakSet 內(nèi)部有多少個(gè)成員,取決于垃圾回收機(jī)制有沒有運(yùn)行,運(yùn)行前后很可能成員個(gè)數(shù)是不一樣的,而垃圾回收機(jī)制何時(shí)運(yùn)行是不可預(yù)測的,因此 ES6 規(guī)定 WeakSet 不可遍歷。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}

//下面的寫法不行
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)

WeakSet 結(jié)構(gòu)有以下三個(gè)方法。

  • WeakSet.prototype.add(value):向 WeakSet 實(shí)例添加一個(gè)新成員。
  • WeakSet.prototype.delete(value):清除 WeakSet 實(shí)例的指定成員。
  • WeakSet.prototype.has(value):返回一個(gè)布爾值,表示某個(gè)值是否在 WeakSet 實(shí)例之中。
    WeakSet 沒有size屬性,沒有辦法遍歷它的成員。
  1. Map
    JavaScript 的對象(Object),本質(zhì)上是鍵值對的集合(Hash 結(jié)構(gòu)),但是傳統(tǒng)上只能用字符串當(dāng)作鍵。這給它的使用帶來了很大的限制。
    為了解決這個(gè)問題,ES6 提供了 Map 數(shù)據(jù)結(jié)構(gòu)。它類似于對象,也是鍵值對的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當(dāng)作鍵。也就是說,Object 結(jié)構(gòu)提供了“字符串—值”的對應(yīng),Map 結(jié)構(gòu)提供了“值—值”的對應(yīng),是一種更完善的 Hash 結(jié)構(gòu)實(shí)現(xiàn)。如果你需要“鍵值對”的數(shù)據(jù)結(jié)構(gòu),Map 比 Object 更合適。
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

作為構(gòu)造函數(shù),Map 也可以接受一個(gè)數(shù)組作為參數(shù)。該數(shù)組的成員是一個(gè)個(gè)表示鍵值對的數(shù)組。

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"

只有對同一個(gè)對象的引用,Map 結(jié)構(gòu)才將其視為同一個(gè)鍵。

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

如果 Map 的鍵是一個(gè)簡單類型的值(數(shù)字、字符串、布爾值),則只要兩個(gè)值嚴(yán)格相等,Map 將其視為一個(gè)鍵,比如0-0就是一個(gè)鍵,布爾值true和字符串true則是兩個(gè)不同的鍵。另外,undefinednull也是兩個(gè)不同的鍵。雖然NaN不嚴(yán)格相等于自身,但 Map 將其視為同一個(gè)鍵。

實(shí)例的屬性和操作方法

  • size屬性 返回成員總數(shù)
  • set(key,value) 設(shè)置鍵值對,返回Map結(jié)構(gòu)
  • get(key) 讀取key對應(yīng)的值,找不到就是undefined
  • has(key) 返回布爾值,表示key是否在Map中
  • delete(key) 刪除某個(gè)鍵,返回true,失敗返回false
  • clear() 清空所有成員,沒有返回值
    Map 結(jié)構(gòu)原生提供三個(gè)遍歷器生成函數(shù)和一個(gè)遍歷方法。
  • keys():返回鍵名的遍歷器。
  • values():返回鍵值的遍歷器。
  • entries():返回所有成員的遍歷器。
  • forEach():遍歷 Map 的所有成員。
    Map 的遍歷順序就是插入順序。遍歷行為基本與set的一致。
    Map可以轉(zhuǎn)為數(shù)組。
const myMap = new Map()
  .set(true, 7)
  .set({foo: 3}, ['abc']);
[...myMap] //[[true, 7], [{foo: 3},["abc"]]]

數(shù)組也可以轉(zhuǎn)為Map

new Map([
  [true, 7],
  [{foo: 3}, ['abc']]
])
// Map {
//   true => 7,
//   Object {foo: 3} => ['abc']
// }

如果所有 Map 的鍵都是字符串,它可以轉(zhuǎn)為對象。

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map()
  .set('yes', true)
  .set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }

對象也可以轉(zhuǎn)為Map

function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}

Map可以轉(zhuǎn)為JSON,但是要分兩種情況。
一種情況是,Map 的鍵名都是字符串,這時(shí)可以選擇轉(zhuǎn)為對象 JSON。

function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

另一種情況是,Map 的鍵名有非字符串,這時(shí)可以選擇轉(zhuǎn)為數(shù)組 JSON。

function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}

let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'

JSON可以轉(zhuǎn)為Map
正常情況下,所有鍵名都是字符串。

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}

但是,有一種特殊情況,整個(gè) JSON 就是一個(gè)數(shù)組,且每個(gè)數(shù)組成員本身,又是一個(gè)有兩個(gè)成員的數(shù)組。這時(shí),它可以一一對應(yīng)地轉(zhuǎn)為 Map。這往往是數(shù)組轉(zhuǎn)為 JSON 的逆操作。

function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
  1. WeakMap
    WeakMap結(jié)構(gòu)與Map結(jié)構(gòu)類似,也是用于生成鍵值對的集合。
    WeakMapMap的區(qū)別有兩點(diǎn):
  • WeakMap只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名。
  • WeakMap的鍵名所指向的對象,不計(jì)入垃圾回收機(jī)制。
    WeakMap的設(shè)計(jì)目的在于,有時(shí)我們想在某個(gè)對象上面存放一些數(shù)據(jù),但是這會(huì)形成對于這個(gè)對象的引用。
const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
  [e1, 'foo 元素'],
  [e2, 'bar 元素'],
];
//e1和e2是兩個(gè)對象,我們通過arr數(shù)組對這兩個(gè)對象添加一些文字說明。這就形成了arr對e1和e2的引用。
//一旦不再需要這兩個(gè)對象,我們就必須手動(dòng)刪除這個(gè)引用,否則垃圾回收機(jī)制就不會(huì)釋放e1和e2占用的內(nèi)存。

WeakMap 就是為了解決這個(gè)問題而誕生的,它的鍵名所引用的對象都是弱引用,即垃圾回收機(jī)制不將該引用考慮在內(nèi)。

const wm = new WeakMap();
const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

WeakMap只有四個(gè)方法可用:get()set()、has()、delete()
無法被遍歷,因?yàn)闆]有size。無法被清空,因?yàn)闆]有clear(),跟WeakSet相似。

let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);

上面代碼中,myElement是一個(gè) DOM 節(jié)點(diǎn),每當(dāng)發(fā)生click事件,就更新一下狀態(tài)。我們將這個(gè)狀態(tài)作為鍵值放在 WeakMap 里,對應(yīng)的鍵名就是myElement。一旦這個(gè) DOM 節(jié)點(diǎn)刪除,該狀態(tài)就會(huì)自動(dòng)消失,不存在內(nèi)存泄漏風(fēng)險(xiǎn)。

三、Proxy

  1. Proxy 用于修改某些操作的默認(rèn)行為,等同于在語言層面做出修改,所以屬于一種“元編程”,即對編程語言進(jìn)行編程。
    Proxy 可以理解成,在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對外界的訪問進(jìn)行過濾和改寫。Proxy 這個(gè)詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。
var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});
//上面代碼對一個(gè)空對象架設(shè)了一層攔截,重定義了屬性的讀取(get)和設(shè)置(set)行為
obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2

上面代碼說明,Proxy 實(shí)際上重載(overload)了點(diǎn)運(yùn)算符,即用自己的定義覆蓋了語言的原始定義。
ES6 原生提供 Proxy 構(gòu)造函數(shù),用來生成 Proxy 實(shí)例。

let proxy = new Proxy(target, handler);

Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數(shù)的寫法。其中,new Proxy()表示生成一個(gè)Proxy實(shí)例,target參數(shù)表示所要攔截的目標(biāo)對象,handler參數(shù)也是一個(gè)對象,用來定制攔截行為。

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

如果handler沒有設(shè)置任何攔截,那就等同于直接通向原對象。

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

上面代碼中,handler是一個(gè)空對象,沒有任何攔截效果,訪問proxy就等同于訪問target。
同一個(gè)攔截器函數(shù),可以設(shè)置攔截多個(gè)操作。
對于可以設(shè)置、但沒有設(shè)置攔截的操作,則直接落在目標(biāo)對象上,按照原先的方式產(chǎn)生結(jié)果。
下面是 Proxy 支持的攔截操作一覽,一共 13 種:

  • get(target, propKey, receiver):攔截對象屬性的讀取,比如proxy.fooproxy['foo']。
  • set(target, propKey, value, receiver):攔截對象屬性的設(shè)置,比如proxy.foo = vproxy['foo'] = v,返回一個(gè)布爾值。
  • has(target, propKey):攔截propKey in proxy的操作,返回一個(gè)布爾值。
  • deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個(gè)布爾值。
  • ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一個(gè)數(shù)組。該方法返回目標(biāo)對象所有自身的屬性的屬性名,而Object.keys()的返回結(jié)果僅包括目標(biāo)對象自身的可遍歷屬性。
  • getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個(gè)布爾值。
  • preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個(gè)布爾值。
  • getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個(gè)對象。
  • isExtensible(target):攔截Object.isExtensible(proxy),返回一個(gè)布爾值。
  • setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個(gè)布爾值。如果目標(biāo)對象是函數(shù),那么還有兩種額外操作可以攔截。
  • apply(target, object, args):攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如proxy(...args)proxy.call(object, ...args)、proxy.apply(...)
  • construct(target, args):攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如new proxy(...args)
    deleteProperty方法用于攔截delete操作,如果這個(gè)方法拋出錯(cuò)誤或者返回false,當(dāng)前屬性就無法被delete命令刪除。
    apply方法攔截函數(shù)的調(diào)用、callapply操作。
    get方法用于攔截某個(gè)屬性的讀取操作。
let obj2 = new Proxy(obj,{
  get(target,property,a){
    //return 35;
    /*console.log(target)
                console.log(property)*/
    let Num = ++wkMap.get(obj).getPropertyNum;
    console.log(`當(dāng)前訪問對象屬性次數(shù)為:${Num}`)
    return target[property]

  },
  deleteProperty(target,property){
    return false;
  },
  apply(target,ctx,args){
    return Reflect.apply(...[target,[],args]);;
  }

})
  1. Proxy.revocable方法返回一個(gè)可取消的 Proxy 實(shí)例。
let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable方法返回一個(gè)對象,該對象的proxy屬性是Proxy實(shí)例,revoke屬性是一個(gè)函數(shù),可以取消Proxy實(shí)例。上面代碼中,當(dāng)執(zhí)行revoke函數(shù)之后,再訪問Proxy實(shí)例,就會(huì)拋出一個(gè)錯(cuò)誤。
Proxy.revocable的一個(gè)使用場景是,目標(biāo)對象不允許直接訪問,必須通過代理訪問,一旦訪問結(jié)束,就收回代理權(quán),不允許再次訪問。

  1. this問題。
    雖然 Proxy 可以代理針對目標(biāo)對象的訪問,但它不是目標(biāo)對象的透明代理,即不做任何攔截的情況下,也無法保證與目標(biāo)對象的行為一致。主要原因就是在 Proxy 代理的情況下,目標(biāo)對象內(nèi)部的this關(guān)鍵字會(huì)指向 Proxy 代理。
const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true
//一旦proxy代理target.m,后者內(nèi)部的this就是指向proxy,而不是target。

四、Reflect

Reflect對象與Proxy對象一樣,也是 ES6 為了操作對象而提供的新 API。
設(shè)計(jì)目的:

  1. Object對象的一些明顯屬于語言內(nèi)部的方法(比如Object.defineProperty),放到Reflect對象上?,F(xiàn)階段,某些方法同時(shí)在ObjectReflect對象上部署,未來的新方法將只部署在Reflect對象上。
  2. 修改某些Object方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時(shí),會(huì)拋出一個(gè)錯(cuò)誤,而Reflect.defineProperty(obj, name, desc)則會(huì)返回false
  3. Object操作都變成函數(shù)行為。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。
  4. Reflect對象的方法與Proxy對象的方法一一對應(yīng),只要是Proxy對象的方法,就能在Reflect對象上找到對應(yīng)的方法。這就讓Proxy對象可以方便地調(diào)用對應(yīng)的Reflect方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說,不管Proxy怎么修改默認(rèn)行為,你總可以在Reflect上獲取默認(rèn)行為。

五、Promise

  1. 概念。
    Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。
    Promise,簡單說就是一個(gè)容器,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。
  2. 特點(diǎn)。
  • 對象的狀態(tài)不受外界影響。
  • 一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果。
  1. 狀態(tài)。
    Promise對象代表一個(gè)異步操作,有三種狀態(tài):
    pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失敗)。
    只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個(gè)狀態(tài)。
  2. 缺點(diǎn)。
  • 無法取消Promise,一旦新建它就會(huì)立即執(zhí)行,無法中途取消。
  • 如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤,不會(huì)反應(yīng)到外部。
  • 當(dāng)處于pending狀態(tài)時(shí),無法得知目前進(jìn)展到哪一個(gè)階段(剛剛開始還是即將完成)。
  1. 用法。
let p = new Promise((resolve,reject)=>{
  //一些異步操作
  setTimeout(()=>{
    console.log("123")
    resolve("abc");
  },0)
})
.then(function(data){
  //resolve狀態(tài)
  console.log(data)
},function(err){
  //reject狀態(tài)
})
//'123'
//'abc'

Promise實(shí)例生成以后,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)。
也就是說,狀態(tài)由實(shí)例化時(shí)的參數(shù)(函數(shù))執(zhí)行來決定的,根據(jù)不同的狀態(tài),看看需要走then的第一個(gè)參數(shù)還是第二個(gè)。
resolve()和reject()的參數(shù)會(huì)傳遞到對應(yīng)的回調(diào)函數(shù)的data或err。

  1. 鏈?zhǔn)讲僮鞯挠梅ā?br> 從表面上看,Promise只是能夠簡化層層回調(diào)的寫法,而實(shí)質(zhì)上,Promise的精髓是“狀態(tài)”,用維護(hù)狀態(tài)、傳遞狀態(tài)的方式來使得回調(diào)函數(shù)能夠及時(shí)調(diào)用,它比傳遞callback函數(shù)要簡單、靈活的多。所以使用Promise的正確場景是這樣的:
runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});
//異步任務(wù)1執(zhí)行完成
//隨便什么數(shù)據(jù)1
//異步任務(wù)2執(zhí)行完成
//隨便什么數(shù)據(jù)2
//異步任務(wù)3執(zhí)行完成
//隨便什么數(shù)據(jù)3

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)1執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)2執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)2');
        }, 2000);
    });
    return p;            
}
function runAsync3(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)3執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)3');
        }, 2000);
    });
    return p;            
}

在then方法中,你也可以直接return數(shù)據(jù)而不是Promise對象,在后面的then中也可以接收到數(shù)據(jù):

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return '直接返回?cái)?shù)據(jù)';  //這里直接返回?cái)?shù)據(jù)
})
.then(function(data){
    console.log(data);
});
//異步任務(wù)1執(zhí)行完成
//隨便什么數(shù)據(jù)1
//異步任務(wù)2執(zhí)行完成
//隨便什么數(shù)據(jù)2
//直接返回?cái)?shù)據(jù)
  1. reject的用法。
    前面的例子都是只有“執(zhí)行成功”的回調(diào),還沒有“失敗”的情況,reject的作用就是把Promise的狀態(tài)置為rejected,這樣我們在then中就能捕捉到,然后執(zhí)行“失敗”情況的回調(diào)。
let num = 10;
let p1 = function() {
    return new Promise((resolve,reject)=>{
      if (num <= 5) {
        resolve("<=5,走resolce")
        console.log('resolce不能結(jié)束Promise')
      }else{
        reject(">5,走reject")
        console.log('reject不能結(jié)束Promise')
      }
    }) 
}

p1()
  .then(function(data){
    console.log(data)
  },function(err){
    console.log(err)
  })
//reject不能結(jié)束Promise
//>5,走reject

resolve和reject永遠(yuǎn)會(huì)在當(dāng)前環(huán)境的最后執(zhí)行,所以后面的同步代碼會(huì)先執(zhí)行。
如果resolve和reject之后還有代碼需要執(zhí)行,最好放在then里。
然后在resolve和reject前面寫上return。

  1. Promise.prototype.catch
    Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。
p1()
  .then(function(data){
    console.log(data)
  })
  .catch(function(err){
    console.log(err)
  })
//reject不能結(jié)束Promise
//>5,走reject    
  1. Promise.all()
    Promise.all方法用于將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。
const p = Promise.all([p1, p2, p3]);

p的狀態(tài)由p1p2、p3決定,分成兩種情況。

  • 只有p1、p2p3的狀態(tài)都變成resolve,p的狀態(tài)才會(huì)變成resolve。 此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)。
  • 只要p1p2、p3之中有一個(gè)被rejectedp的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。
    promises是包含 3 個(gè) Promise 實(shí)例的數(shù)組,只有這 3 個(gè)實(shí)例的狀態(tài)都變成resolve,或者其中有一個(gè)變?yōu)?code>rejected,才會(huì)調(diào)用Promise.all方法后面的回調(diào)函數(shù)。
    如果作為參數(shù)的 Promise 實(shí)例,自己定義了catch方法,那么它一旦被rejected,并不會(huì)觸發(fā)Promise.all()catch方法,如果沒有參數(shù)沒有定義自己的catch,就會(huì)調(diào)用Promise.all()catch方法。
  1. Promise.race。
    Promise.race方法同樣是將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。
const p = Promise.race([p1, p2, p3]);

上面代碼中,只要p1、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的 Promise 實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。

  1. Promise.resolve()。
    有時(shí)需要將現(xiàn)有對象轉(zhuǎn)為 Promise 對象,Promise.resolve方法就起到這個(gè)作用。下面代碼將123轉(zhuǎn)為一個(gè) Promise 對象。
const jsPromise = Promise.resolve('123');

Promise.resolve等價(jià)于下面的寫法。

Promise.resolve('123')
// 等價(jià)于
new Promise(resolve => resolve('123'))

Promise.resolve方法的參數(shù)分成四種情況。

  • 參數(shù)是一個(gè) Promise 實(shí)例
    如果參數(shù)是 Promise 實(shí)例,那么Promise.resolve將不做任何修改、原封不動(dòng)地返回這個(gè)實(shí)例。
  • 參數(shù)是一個(gè)thenable對象。
    thenable對象指的是具有then方法的對象,比如下面這個(gè)對象。
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

Promise.resolve方法會(huì)將這個(gè)對象轉(zhuǎn)為 Promise 對象,然后就立即執(zhí)行thenable對象的then方法。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

上面代碼中,thenable對象的then方法執(zhí)行后,對象p1的狀態(tài)就變?yōu)?code>resolved,從而立即執(zhí)行最后那個(gè)then方法指定的回調(diào)函數(shù),輸出 42。

  • 參數(shù)不是具有then方法的對象,或根本就不是對象
    如果參數(shù)是一個(gè)原始值,或者是一個(gè)不具有then方法的對象,則Promise.resolve方法返回一個(gè)新的 Promise 對象,狀態(tài)為resolved
const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello

上面代碼生成一個(gè)新的 Promise 對象的實(shí)例p。由于字符串Hello不屬于異步操作(判斷方法是字符串對象不具有 then 方法),返回 Promise 實(shí)例的狀態(tài)從一生成就是resolved,所以回調(diào)函數(shù)會(huì)立即執(zhí)行。Promise.resolve方法的參數(shù),會(huì)同時(shí)傳給回調(diào)函數(shù)。

  • 不帶有任何參數(shù)
    Promise.resolve方法允許調(diào)用時(shí)不帶參數(shù),直接返回一個(gè)resolved狀態(tài)的 Promise 對象。
    所以,如果希望得到一個(gè) Promise 對象,比較方便的方法就是直接調(diào)用Promise.resolve方法。
const p = Promise.resolve();

p.then(function () {
  // ...
});

上面代碼的變量p就是一個(gè) Promise 對象。
需要注意的是,立即resolve的 Promise 對象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時(shí),而不是在下一輪“事件循環(huán)”的開始時(shí)。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

上面代碼中,setTimeout(fn, 0)在下一輪“事件循環(huán)”開始時(shí)執(zhí)行,Promise.resolve()在本輪“事件循環(huán)”結(jié)束時(shí)執(zhí)行,console.log('one')則是立即執(zhí)行,因此最先輸出。

  1. Promise.reject()。
    Promise.reject(reason)方法也會(huì)返回一個(gè)新的 Promise 實(shí)例,該實(shí)例的狀態(tài)為rejected。
const p = Promise.reject('出錯(cuò)了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯(cuò)了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯(cuò)了

上面代碼生成一個(gè) Promise 對象的實(shí)例p,狀態(tài)為rejected,回調(diào)函數(shù)會(huì)立即執(zhí)行。
注意,Promise.reject()方法的參數(shù),會(huì)原封不動(dòng)地作為reject的理由,變成后續(xù)方法的參數(shù)。這一點(diǎn)與Promise.resolve方法不一致。

const thenable = {
  then(resolve, reject) {
    reject('出錯(cuò)了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

上面代碼中,Promise.reject方法的參數(shù)是一個(gè)thenable對象,執(zhí)行以后,后面catch方法的參數(shù)不是reject拋出的“出錯(cuò)了”這個(gè)字符串,而是thenable對象。

六、Fetch

  1. 傳統(tǒng)Ajax指的是XMLHttpRequest(XHR),現(xiàn)在和將來會(huì)被Fetch取代。
    XMLHttpRequest是一個(gè)設(shè)計(jì)粗糙的API,配置和調(diào)用方式非?;靵y,而且基于事件的異步模型寫起來也沒有現(xiàn)代的Promise,generator/yield,async/await友好。
    Fetch的出現(xiàn)就是為了解決XHR的問題。但是Fetch API是基于Promise設(shè)計(jì),舊瀏覽器不支持Promise,需要使用polyfill es6-promise
    使用XHR發(fā)送一個(gè)json請求一般是這樣:
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';

xhr.onload = function(){
    console.log(xhr.response);
};

xhr.onerror = function(){
    console.log("Oops, error");
};

xhr.send();

使用Fetch后:

fetch(url)
    .then(function(response){
        return response.json();
    }).then(function(data){
        console.log(data);
    }).catch(function(e){
        console.log("Oops, error");
    });

使用ES6的箭頭函數(shù)后:

fetch(url)
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(e => console.log("Oops, error", e))

使用async/await來做最終優(yōu)化:

try{
    let response = await fetch(url);
    let data = await response.json();
    console.log(data);
}catch(e){
    console.log("Oops, error", e);
}
//注:這段代碼如果想運(yùn)行,外面需要包一個(gè)async function
  1. 用法
fetch(url, options).then(function(response){
    //handle HTTP response
}, function(error){
    //handle network error
})

url:定義要獲取的資源。這可能是:

  • 一個(gè)USVString字符串,包含要獲取資源的URL。
  • 一個(gè)Request對象。
    options(可選)
    一個(gè)配置項(xiàng)對象,包括所有對請求的設(shè)置,可選的參數(shù)有:
  • method:請求使用的方法,如GET、POST。
  • headers:請求的頭信息,形式為Headers對象或ByteString。
  • body:請求的body信息,可能是一個(gè)Blob、BufferSource、FormData、URLSearchParams或者USVString對象。注意GET或HEAD方法的請求不能包含body信息。
  • mode:請求的模式,如cors、no-cors或者same-origin。
  • credentials:請求的credentials,如omit、same-origin或者include。
  • cache:請求的cache模式:default,no-store,reload,no-cache,force-cache,或者only-if-cached。

response:
一個(gè)Promise,resolve時(shí)回傳Response對象:
屬性:

  • status(number)-HTTP請求結(jié)果參數(shù),在100~599范圍。
  • statusText(String)-服務(wù)器返回的狀態(tài)報(bào)告。
  • ok(boolean)-如果返回200表示請求成功則為true。
  • headers(Headers)-返回頭部信息,下面詳細(xì)介紹。
  • url(String)-請求的地址。
    方法:
  • text() -以string的形式生成請求text。
  • json() -生成JSON.parse(responseText)的結(jié)果。
  • blob() -生成一個(gè)Blob。
  • arrayBuffer() -生成一個(gè)ArrayBuffer。
  • formData() -生成格式化的數(shù)據(jù),可用于其他的請求。
    其他方法:
  • clone() -創(chuàng)建一個(gè)Response對象的克隆。
  • Response.error() -返回一個(gè)綁定了網(wǎng)絡(luò)錯(cuò)誤的新的Response對象。
  • Response.redirect() -用另一個(gè)url創(chuàng)建一個(gè)新的response。
    response.headers:
  • has(name)(boolean) -判斷是否存在該信息頭。
  • get(name)(String) -獲取信息頭的數(shù)據(jù)。
  • getAll(name)(Array) -獲取所有頭部數(shù)據(jù)。
  • set(name, value) -設(shè)置信息頭的參數(shù)。
    append(name, value) -添加header的內(nèi)容。
    delete(name) -刪除header的信息。
    forEach(function(value, name){...},[thisContext]) -循環(huán)讀取header的信息。
    get
fetch('/users.html')
    .then(function(response){
        return response.text();
    }).then(function(body){
        document.body.innerHTML = body
    })

post

fetch('/users', {
    method:'POST',
    headers:{
        'Accept':'application/json',
        'Content-Type':'application/json'
    },
    body:JSON.stringify({
        name:'Hubot',
        login:'hubot',
    })
})
  1. Fetch優(yōu)點(diǎn)主要有:
  • 語法簡潔,更加語義化。
  • 基于標(biāo)準(zhǔn)Promise實(shí)現(xiàn),支持async/await
  1. Fetch的原生支持率不高,需要引入下面的polyfill后才完美支持IE8+:
  • 由于IE8是ES3,需要引入ES5的polyfill:es5-shim,es5-sham
  • 引入Promise的polyfill:es6-promise
  • 引入fetch探測庫:fetch-detector
  • 引入fetch的polyfill:fetch-ie8
  • 可選:如果還使用了jsonp,引入fetch-jsonp
  • 可選:開啟Babel的runtime模式,現(xiàn)在就使用async/await
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文為阮一峰大神的《ECMAScript 6 入門》的個(gè)人版提純! babel babel負(fù)責(zé)將JS高級語法轉(zhuǎn)義,...
    Devildi已被占用閱讀 2,131評論 0 4
  • 第一章:塊級作用域綁定 塊級聲明 1.var聲明及變量提升機(jī)制:在函數(shù)作用域或者全局作用域中通過關(guān)鍵字var聲明的...
    BeADre_wang閱讀 996評論 0 0
  • [TOC] 參考阮一峰的ECMAScript 6 入門參考深入淺出ES6 let和const let和const都...
    郭子web閱讀 1,915評論 0 1
  • ES6特性歸納 ES的全稱是ECMAScript,它是JavaScript的規(guī)格,JS是ES的一種實(shí)現(xiàn)。ES還有J...
    FWHeart閱讀 602評論 0 4
  • 實(shí)話生活 體悟人生 今天是2017年2月24日 天氣陰轉(zhuǎn)晴天 溫度9-13度 今天早上四點(diǎn)多起床 起來了以后 先按...
    木風(fēng)恒閱讀 139評論 0 0

友情鏈接更多精彩內(nèi)容