#ECMASCRIPT6筆記

Ox9A82發表於2017-08-11

ECMASCRIPT6筆記

來源於http://es6.ruanyifeng.com/#docs/proxy
是我在閱讀時做下的筆記,方便以後查閱

Symbol

ES5 的物件屬性名都是字串,這容易造成屬性名的衝突。比如,你使用了一個他人提供的物件,但又想為這個物件新增新的方法(mixin 模式),新方法的名字就有可能與現有方法產生衝突。如果有一種機制,保證每個屬性的名字都是獨一無二的就好了,這樣就從根本上防止屬性名的衝突。這就是 ES6 引入Symbol的原因。
ES6 引入了一種新的原始資料型別Symbol
Symbol 值透過Symbol函式生成。這就是說,物件的屬性名現在可以有兩種型別,一種是原來就有的字串,另一種就是新增的 Symbol 型別。凡是屬性名屬於 Symbol 型別,就都是獨一無二的,可以保證不會與其他屬性名產生衝突。
由於每一個 Symbol 值都是不相等的,這意味著 Symbol 值可以作為識別符號,用於物件的屬性名,就能保證不會出現同名的屬性。這對於一個物件由多個模組構成的情況非常有用,能防止某一個鍵被不小心改寫或覆蓋。

var mySymbol = Symbol();

// 第一種寫法
var a = {};
a[mySymbol] = 'Hello!';

// 第二種寫法
var a = {
  [mySymbol]: 'Hello!'//用[]圍起來定義,否則會被認為是字串
};

// 第三種寫法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上寫法都得到同樣結果
a[mySymbol] // "Hello!"

注意,Symbol 值作為物件屬性名時,不能用點運算子。

var mySymbol = Symbol();
var a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

Symbol 作為屬性名,該屬性不會出現在for...in、for...of迴圈中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,可以獲取指定物件的所有 Symbol 屬性名。

Symbol.for()

有時,我們希望重新使用同一個Symbol值,Symbol.for方法可以做到這一點。它接受一個字串作為引數,然後搜尋有沒有以該引數作為名稱的Symbol值。如果有,就返回這個Symbol值,否則就新建並返回一個以該字串為名稱的Symbol值。

var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');

s1 === s2 // true
Symbol.for("bar") === Symbol.for("bar")
// true

Symbol("bar") === Symbol("bar")
// false

上面程式碼中,由於Symbol()寫法沒有登記機制,所以每次呼叫都會返回一個不同的值。
需要注意的是,Symbol.for為Symbol值登記的名字,是全域性環境的,可以在不同的 iframe 或 service worker 中取到同一個值。

iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);

iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
// true

上面程式碼中,iframe 視窗生成的 Symbol 值,可以在主頁面得到。

Symbol.keyFor

Symbol.keyFor方法返回一個已登記的 Symbol 型別值的key。

var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

上面程式碼中,變數s2屬於未登記的Symbol值,所以返回undefined。


Set

新的資料結構 Set,它類似於陣列,但是成員的值都是唯一的,沒有重複的值。

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 可以看到沒有重複值

Set 結構的例項有以下屬性。

Set.prototype.constructor:建構函式,預設就是Set函式。
Set.prototype.size:返回Set例項的成員總數。

Set 例項的方法分為兩大類:操作方法(用於運算元據)和遍歷方法(用於遍歷成員)。下面先介紹四個操作方法。

add(value):新增某個值,返回Set結構本身。
delete(value):刪除某個值,返回一個布林值,表示刪除是否成功。
has(value):返回一個布林值,表示該值是否為Set的成員。
clear():清除所有成員,沒有返回值。

s.add(1).add(2).add(2);
// 注意2被加入了兩次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false

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 結構的例項預設可遍歷,它的預設遍歷器生成函式就是它的values方法。

Set.prototype[Symbol.iterator] === Set.prototype.values
// true

這意味著,可以省略values方法,直接用for...of迴圈遍歷 Set。

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
  console.log(x);
}
// red
// green
// blue

for...of遍歷陣列之類的結構

for(var a of array)
{
    console.log(a);
}

forEach()

let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(value * 2) )
// 2
// 4
// 6

上面程式碼說明,forEach方法的引數就是一個處理函式。該函式的引數依次為鍵值、鍵名、集合本身(上例省略了該引數)。另外,forEach方法還可以有第二個引數,表示繫結的this物件。


WeakSet

WeakSet 結構與 Set 類似,也是不重複的值的集合。但是,它與 Set 有兩個區別。
首先,WeakSet 的成員只能是物件,而不能是其他型別的值。

const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set

其次,WeakSet 中的物件都是弱引用,即垃圾回收機制不考慮 WeakSet 對該物件的引用,也就是說,如果其他物件都不再引用該物件,那麼垃圾回收機制會自動回收該物件所佔用的記憶體,不考慮該物件還存在於 WeakSet 之中。
WeakSet 是一個建構函式,可以使用new命令,建立 WeakSet 資料結構。

const ws = new WeakSet();

作為建構函式,WeakSet 可以接受一個陣列或類似陣列的物件作為引數。(實際上,任何具有 Iterable 介面的物件,都可以作為 WeakSet 的引數。)該陣列的所有成員,都會自動成為 WeakSet 例項物件的成員。

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}

注意,是a陣列的成員成為 WeakSet 的成員,而不是a陣列本身。這意味著,陣列的成員只能是物件。


Proxy

Proxy 用於修改某些操作的預設行為,等同於在語言層面做出修改
Proxy 可以理解成,在目標物件之前架設一層“攔截”,外界對該物件的訪問,都必須先透過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。

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);
  }
});
//set和get是自有的屬性

對一個空物件架設了一層攔截,重定義了屬性的讀取(get)和設定(set)行為。

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2

上面程式碼說明,Proxy 實際上過載(overload)了點運算子,即用自己的定義覆蓋了語言的原始定義。

var proxy = new Proxy(target, handler);

target參數列示所要攔截的目標物件,handler引數也是一個物件,用來定製攔截行為。

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

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

比如,上面程式碼中,配置物件有一個get方法,用來攔截對目標物件屬性的訪問請求。get方法的兩個引數分別是目標物件和所要訪問的屬性。可以看到,由於攔截函式總是返回35,所以訪問任何屬性都得到35。
注意,要使得Proxy起作用,必須針對Proxy例項(上例是proxy物件)進行操作,而不是針對目標物件(上例是空物件)進行操作。

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

如果handler沒有設定任何攔截,那就等同於直接通向原物件。
將 Proxy 物件,設定到object.proxy屬性,從而可以在object物件上呼叫。

var object = { proxy: new Proxy(target, handler) };

Proxy 例項也可以作為其他物件的原型物件。

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

let obj = Object.create(proxy);
obj.time // 35

上面程式碼中,proxy物件是obj物件的原型,obj物件本身並沒有time屬性,所以根據原型鏈,會在proxy物件上讀取該屬性,導致被攔截。
下面是 Proxy 支援的攔截操作一覽。

對於可以設定、但沒有設定攔截的操作,則直接落在目標物件上,按照原先的方式產生結果。

(1)get(target, propKey, receiver)

攔截物件屬性的讀取,比如proxy.foo和proxy['foo']。

最後一個引數receiver是一個物件,可選,參見下面Reflect.get的部分。

(2)set(target, propKey, value, receiver)

攔截物件屬性的設定,比如proxy.foo = v或proxy['foo'] = v,返回一個布林值。

(3)has(target, propKey)

攔截propKey in proxy的操作,返回一個布林值。

(4)deleteProperty(target, propKey)

攔截delete proxy[propKey]的操作,返回一個布林值。

(5)ownKeys(target)

攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一個陣列。該方法返回目標物件所有自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標物件自身的可遍歷屬性。

(6)getOwnPropertyDescriptor(target, propKey)

攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述物件。

(7)defineProperty(target, propKey, propDesc)

攔截Object.defineProperty(proxy, propKey,
propDesc)、Object.defineProperties(proxy, propDescs),返回一個布林值。

(8)preventExtensions(target)

攔截Object.preventExtensions(proxy),返回一個布林值。

(9)getPrototypeOf(target)

攔截Object.getPrototypeOf(proxy),返回一個物件。

(10)isExtensible(target)

攔截Object.isExtensible(proxy),返回一個布林值。

(11)setPrototypeOf(target, proto)


,返回一個布林值。

如果目標物件是函式,那麼還有兩種額外操作可以攔截。

(12)apply(target, object, args)

攔截 Proxy 例項作為函式呼叫的操作,比如proxy(...args)、proxy.call(object,
...args)、proxy.apply(...)。

(13)construct(target, args)

攔截 Proxy 例項作為建構函式呼叫的操作,比如new proxy(...args)。


Reflect

Reflect物件與Proxy物件一樣,也是 ES6 為了操作物件而提供的新 API。Reflect物件的設計目的有這樣幾個。
(1) 將Object物件的一些明顯屬於語言內部的方法(比如Object.defineProperty),放到Reflect物件上。現階段,某些方法同時在Object和Reflect物件上部署,未來的新方法將只部署在Reflect物件上。也就是說,從Reflect物件上可以拿到語言內部的方法。
(2) 修改某些Object方法的返回結果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會丟擲一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false。

// 老寫法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3) 讓Object操作都變成函式行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函式行為。

// 老寫法
'assign' in Object // true

// 新寫法
Reflect.has(Object, 'assign') // true

(4)Reflect物件的方法與Proxy物件的方法一一對應,只要是Proxy物件的方法,就能在Reflect物件上找到對應的方法。這就讓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)

上面這些方法的作用,大部分與Object物件的同名方法的作用都是相同的,而且它與Proxy物件的方法是一一對應的。下面是對它們的解釋。