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]

  1. Set 结构的实例有四个遍历方法,可以用于遍历成员。
  2. - Set.prototype.keys():返回键名的遍历器
  3. - Set.prototype.values():返回键值的遍历器
  4. - Set.prototype.entries():返回键值对的遍历器
  5. - Set.prototype.forEach():使用回调函数遍历每个成员
  6. 由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致
  7. ```javascript
  8. let set = new Set(['red', 'green', 'blue']);
  9. for (let item of set.keys()) {
  10. console.log(item);
  11. }
  12. // red
  13. // green
  14. // blue
  15. for (let item of set.values()) {
  16. console.log(item);
  17. }
  18. // red
  19. // green
  20. // blue
  21. for (let item of set.entries()) {
  22. console.log(item);
  23. }
  24. // ["red", "red"]
  25. // ["green", "green"]
  26. // ["blue", "blue"]

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法
这意味着,可以省略values方法,直接用for…of循环遍历 Set

  1. let set = new Set(['red', 'green', 'blue']);
  2. for (let x of set) {
  3. console.log(x);
  4. }
  5. // red
  6. // green
  7. // blue

应用

扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构。

  1. let set = new Set(['red', 'green', 'blue']);
  2. let arr = [...set];
  3. // ['red', 'green', 'blue']

数组的map和filter方法也可以间接用于 Set 了。

  1. let set = new Set([1, 2, 3]);
  2. set = new Set([...set].map(x => x * 2));
  3. // 返回Set结构:{2, 4, 6}
  4. let set = new Set([1, 2, 3, 4, 5]);
  5. set = new Set([...set].filter(x => (x % 2) == 0));
  6. // 返回Set结构:{2, 4}

应用

  1. let arr = [3, 5, 2, 2, 5, 5];
  2. let unique = [...new Set(arr)]; // 去重 [3, 5, 2]
  3. let a = new Set([1, 2, 3]);
  4. let b = new Set([4, 3, 2]);
  5. // 并集
  6. let union = new Set([...a, ...b]); // Set {1, 2, 3, 4}
  7. // 交集
  8. let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3}
  9. // (a 相对于 b 的)差集
  10. let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}

如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。

  1. // 方法一
  2. let set = new Set([1, 2, 3]);
  3. set = new Set([...set].map(val => val * 2));
  4. // set的值是2, 4, 6
  5. // 方法二
  6. let set = new Set([1, 2, 3]);
  7. set = new Set(Array.from(set, val => val * 2));
  8. // 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 的所有成员。

与其他数据结构的互相转换

  1. // Map 转为数组
  2. const myMap = new Map()
  3. .set(true, 7)
  4. .set({foo: 3}, ['abc']);
  5. [...myMap]// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  6. //数组 转为 Map
  7. new Map([
  8. [true, 7],
  9. [{foo: 3}, ['abc']]
  10. ])
  11. // Map {
  12. // true => 7,
  13. // Object {foo: 3} => ['abc']
  14. // }
  15. // Map 转为对象
  16. function strMapToObj(strMap) {
  17. let obj = Object.create(null);
  18. for (let [k,v] of strMap) {
  19. obj[k] = v;
  20. }
  21. return obj;
  22. }
  23. const myMap = new Map()
  24. .set('yes', true)
  25. .set('no', false);
  26. strMapToObj(myMap)
  27. // { yes: true, no: false }
  28. // 如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
  29. // 对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以对象的键加不加引号都可以。
  30. // 对象转为 Map
  31. let obj = {"a":1, "b":2};
  32. let map = new Map(Object.entries(obj));
  33. // 也可以自己实现一个转换函数。
  34. function objToStrMap(obj) {
  35. let strMap = new Map();
  36. for (let k of Object.keys(obj)) {
  37. strMap.set(k, obj[k]);
  38. }
  39. return strMap;
  40. }
  41. objToStrMap({yes: true, no: false})
  42. // Map {"yes" => true, "no" => false}

Symbol

Symbol 值通过Symbol函数生成。注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。

  1. // 没有参数的情况
  2. let s1 = Symbol();
  3. let s2 = Symbol();
  4. s1 === s2 // false
  5. // 有参数的情况
  6. let s1 = Symbol('foo');
  7. let s2 = Symbol('foo');
  8. s1 === s2 // false

由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名

  1. let mySymbol = Symbol();
  2. // 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
  3. let a = {
  4. [mySymbol]: 'Hello!'
  5. };
  6. // Symbol 值作为对象属性名时,不能用点运算符。
  7. a['mySymbol'] // "Hello!"

Symbol 作为属性名,遍历对象的时候,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名

  1. const obj = {};
  2. let a = Symbol('a');
  3. let b = Symbol('b');
  4. obj[a] = 'Hello';
  5. obj[b] = 'World';
  6. const objectSymbols = Object.getOwnPropertySymbols(obj);
  7. objectSymbols
  8. // [Symbol(a), Symbol(b)]

另一个新的 API,Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

  1. let obj = {
  2. [Symbol('my_key')]: 1,
  3. enum: 2,
  4. nonEnum: 3
  5. };
  6. Reflect.ownKeys(obj)
  7. // ["enum", "nonEnum", Symbol(my_key)]

Symbol.for()

Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。

  1. Symbol.for("bar") === Symbol.for("bar")
  2. // true
  3. Symbol("bar") === Symbol("bar")
  4. // false

Symbol.keyFor()

Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。

  1. let s1 = Symbol.for("foo"); // Symbol.for()为 Symbol 值登记的名字,
  2. // 是全局环境的,不管有没有在全局环境运行。
  3. Symbol.keyFor(s1) // "foo"

Proxy

  1. var proxy = new Proxy(target, handler);

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

  1. var obj = new Proxy({}, {
  2. get: function (target, propKey, receiver) {
  3. console.log(`getting ${propKey}!`);
  4. return Reflect.get(target, propKey, receiver);
  5. },
  6. set: function (target, propKey, value, receiver) {
  7. console.log(`setting ${propKey}!`);
  8. return Reflect.set(target, propKey, value, receiver);
  9. }
  10. });
  11. obj.count = 1
  12. // setting count!
  13. ++obj.count
  14. // getting count!
  15. // setting count!
  16. // 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对象的方法是一一对应的