Set
Set 结构的实例有以下属性。
- Set.prototype.constructor:构造函数,Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
 - Set.prototype.size:返回Set实例的成员总数。
 
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
- Set.prototype.add(value):添加某个值,返回 Set 结构本身。
 - Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
 - Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
 - Set.prototype.clear():清除所有成员,没有返回值。 ```javascript let s = new Set(); s.add(1).add(2).add(2); // 注意2被加入了两次
 
s.size // 2
s.has(1) // true s.has(2) // true
const set = new Set([1, 2, 3, 4, 4]); […set] // [1, 2, 3, 4]
Set 结构的实例有四个遍历方法,可以用于遍历成员。- Set.prototype.keys():返回键名的遍历器- Set.prototype.values():返回键值的遍历器- Set.prototype.entries():返回键值对的遍历器- Set.prototype.forEach():使用回调函数遍历每个成员由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致```javascriptlet set = new Set(['red', 'green', 'blue']);for (let item of set.keys()) {console.log(item);}// red// green// bluefor (let item of set.values()) {console.log(item);}// red// green// bluefor (let item of set.entries()) {console.log(item);}// ["red", "red"]// ["green", "green"]// ["blue", "blue"]
Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法
这意味着,可以省略values方法,直接用for…of循环遍历 Set
let set = new Set(['red', 'green', 'blue']);for (let x of set) {console.log(x);}// red// green// blue
应用
扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构。
let set = new Set(['red', 'green', 'blue']);let arr = [...set];// ['red', 'green', 'blue']
数组的map和filter方法也可以间接用于 Set 了。
let set = new Set([1, 2, 3]);set = new Set([...set].map(x => x * 2));// 返回Set结构:{2, 4, 6}let set = new Set([1, 2, 3, 4, 5]);set = new Set([...set].filter(x => (x % 2) == 0));// 返回Set结构:{2, 4}
应用
let arr = [3, 5, 2, 2, 5, 5];let unique = [...new Set(arr)]; // 去重 [3, 5, 2]let a = new Set([1, 2, 3]);let b = new Set([4, 3, 2]);// 并集let union = new Set([...a, ...b]); // Set {1, 2, 3, 4}// 交集let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3}// (a 相对于 b 的)差集let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}
如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。
// 方法一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
WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
ES6 规定 WeakSet 不可遍历。
WeakSet 结构有以下三个方法。
- WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
 - WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
 - WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
 
WeakSet 没有size属性,没有办法遍历它的成员。
Map
Map 结构的实例有以下属性。
- Set.prototype.constructor:构造函数,Map函数可以接受一个二维数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
 - Map.prototype.size:返回Set实例的成员总数。
 
Map 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
- Map.prototype.add(key,value):添加某个值,返回 Map 结构本身。
 - Map.prototype.delete(key):删除某个值,返回一个布尔值,表示删除是否成功。
 - Map.prototype.has(key):返回一个布尔值,表示该值是否为Map的成员。
 - SMap.prototype.clear():清除所有成员,没有返回值
 
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
- Map.prototype.keys():返回键名的遍历器。
 - Map.prototype.values():返回键值的遍历器。
 - Map.prototype.entries():返回所有成员的遍历器。
 - Map.prototype.forEach():遍历 Map 的所有成员。
 
与其他数据结构的互相转换
// Map 转为数组const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);[...myMap]// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]//数组 转为 Mapnew Map([[true, 7],[{foo: 3}, ['abc']]])// Map {// true => 7,// Object {foo: 3} => ['abc']// }// Map 转为对象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 }// 如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。// 对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以对象的键加不加引号都可以。// 对象转为 Maplet obj = {"a":1, "b":2};let map = new Map(Object.entries(obj));// 也可以自己实现一个转换函数。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}
Symbol
Symbol 值通过Symbol函数生成。注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。
// 没有参数的情况let s1 = Symbol();let s2 = Symbol();s1 === s2 // false// 有参数的情况let s1 = Symbol('foo');let s2 = Symbol('foo');s1 === s2 // false
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名
let mySymbol = Symbol();// 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。let a = {[mySymbol]: 'Hello!'};// Symbol 值作为对象属性名时,不能用点运算符。a['mySymbol'] // "Hello!"
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名
const obj = {};let a = Symbol('a');let b = Symbol('b');obj[a] = 'Hello';obj[b] = 'World';const objectSymbols = Object.getOwnPropertySymbols(obj);objectSymbols// [Symbol(a), Symbol(b)]
另一个新的 API,Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {[Symbol('my_key')]: 1,enum: 2,nonEnum: 3};Reflect.ownKeys(obj)// ["enum", "nonEnum", Symbol(my_key)]
Symbol.for()
Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
Symbol.for("bar") === Symbol.for("bar")// trueSymbol("bar") === Symbol("bar")// false
Symbol.keyFor()
Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。
let s1 = Symbol.for("foo"); // Symbol.for()为 Symbol 值登记的名字,// 是全局环境的,不管有没有在全局环境运行。Symbol.keyFor(s1) // "foo"
Proxy
var proxy = new Proxy(target, handler);
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy({}, {get: function (target, propKey, receiver) {console.log(`getting ${propKey}!`);return Reflect.get(target, propKey, receiver);},set: function (target, propKey, value, receiver) {console.log(`setting ${propKey}!`);return Reflect.set(target, propKey, value, receiver);}});obj.count = 1// setting count!++obj.count// getting count!// setting count!// 2
Proxy 支持的拦截操作一览,一共 13 种。
- get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
 - set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
 - has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
 - 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),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。
 
Reflect
Reflect对象的设计目的有这样几个。
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为
(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对象的方法是一一对应的
