1. 程式人生 > >ES6語法筆記(Symbol、Set、Map、Proxy、Reflect、Promise)

ES6語法筆記(Symbol、Set、Map、Proxy、Reflect、Promise)

item 鍵值 getdate 循環 清除 blue sin led 返回對象

**以下內容均摘自ECMAScript 6 入門——阮一峰

一、Symbol數據類型(undefinednull、布爾值(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...infor...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.fooproxy[‘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 = vproxy[‘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 objdelete 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構造函數接受一個函數作為參數,該函數的兩個參數分別是resolvereject。它們是兩個函數,由 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]);

只有p1p2p3的狀態都變成fulfilledp的狀態才會變成fulfilled,此時p1p2p3的返回值組成一個數組,傳遞給p的回調函數。

只要p1p2p3之中有一個被rejectedp的狀態就變成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)