ES6--Set、Map、Symbol、Proxy及Reflect

12 篇文章 13 订阅

九、Set和Map数据结构

Set

ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。之前的博文曾阐述过使用ES5实现JavaScript数据结构-集合

new Set([iterable]);
var items = new Set([1,2,3,4,5,5,5,5]);
console.log(items.size);  // 5
console.log(items);       // Set {1, 2, 3, 4, 5}
console.log([...items]);  // [1, 2, 3, 4, 5]
方法说明
add(value)在Set对象尾部添加一个元素,返回该Set对象
clear()移除Set对象内的所有元素,没有返回值
delete(value)移除Set的中与这个值相等的元素,返回一个布尔值,表示删除是否成功
has(value)返回一个布尔值,表示该值是否为Set的成员
keys()/values()返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值
entries()返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组
forEach(callbackFn[, thisArg])按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数

示例:去除数组的重复成员

var ary = [1, 2, 3, 3, 2, 1, "1"];
[...new Set(ary)];  // [1, 2, 3, "1"]

注意:向Set加入值的时候,不会发生类型转换,所以5和”5”是两个不同的值。

WeakSet

WeakSet结构与Set类似,WeakMap结构与Map结构基本类似。WeakSet的成员只能是对象;WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet中对象的引用,其意味着WeakSet不可遍历、不会引发内存泄漏。

var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo); // false,foo尚未添加到集合中

ws.delete(window); //从集合中删除窗口
ws.has(window); // false,窗口已被删除

Map

Map数据结构是一个简单的键/值映射。解决了对象中只能用字符串当键的限制(对象和原始值都可以用作键或值)。

方法说明
size返回成员总数
set(key, value)返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。
get(key)读取key对应的键值,如果找不到key,返回undefined
has(key)返回一个布尔值,表示某个键是否在Map数据结构中
delete(key)删除某个键,返回true。如果删除失败,返回false
clear()清除所有成员,没有返回值
keys()返回由key组成的新Iterator对象
values()返回由value组成的新Iterator对象
entries()返回由[key, value]组成的新Iterator对象
forEach()遍历Map所有成员
let map = new Map();
map.set('stringkey', '123');
map.set('objectKey', {a: '123'});

for(let item of map.entries()) {
    console.log(item);
}

注意: map.entries()map在迭代中效果一致;区别是前者返回新的Iterator对象(具有next()方法),而map只是部署了Symbol.iterator接口所有可以遍历。通过map[Symbol.iterator]()可以获取map的遍历器对象。

Map转换

(1)Map转为数组

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

(2)数组转为Map

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

(3)Map转为对象

function strMapToObj(strMap) {
  let obj = Object.create(null); // 创建空对象
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap);

(4)对象转为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});

(5)Map转为JSON
一种情况是,Map的键名都是字符串,这时可以选择转为对象JSON。

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

let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap);

另一种情况是,Map的键名有非字符串,这时可以选择转为数组JSON。

function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap);

(6)JSON转为Map
键名都是字符串

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes":true,"no":false}');

WeakMap

WeakMap结果与Map结构基本一致,区别在于WeakMap只接受对象作为键名(null除外),而且其键名所指向的对象不计入垃圾回收机制(弱类型)。

十、Iterator和for…of循环

ES6之前表示“集合”的数据结构,主要是数组对象,ES6中新增了MapSet。需要一种统一的接口机制来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。

Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。

function makeIterator(ary){
  var nextIndex = 0;
  return {
    next: function(){
      return nextIndex < ary.length ? 
        { value: ary[nextIndex++], done: false } :
        { value: undefined, done: true };
    }
  };
}
var it = makeIterator(["1", "2"]);
it.next(); // Object {value: "1", done: false}
it.next(); // Object {value: "2", done: false}
it.next(); // Object {value: undefined, done: true}

遍历器(Iterator)本质上是一个指针对象,指向当前数据结构的起始位置。第一次调用对象的next方法,指针指向数据结构的第一个成员;第二次调用next方法,指针指向数据结构的第二个成员;一次类推,直到指向数据结构的结束位置。

在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for…of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性。在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。调用Symbol.iterator接口,就会返回一个遍历器对象。

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next();

调用Iterator接口的场景

一些场景会默认调用Iterator接口(即Symbol.iterator方法)

(1)解构赋值

let set = new Set();
set.add(1).add(2);
let [x, y] = set;
console.log(x, y); // 1 2

(2)扩展运算符

let str = 'hello';
console.log([...str]); // ["h", "e", "l", "l", "o"]

(3)其他场合

由于数组的遍历都会调用遍历器接口,所以任何接受数组作为参数的场合其实都调用了遍历器接口。

  • for…of
  • Array.from(arguments)
  • Map([[‘a’, 1], [‘b’, 2]])、Set([1, 2, 3])、WeakMap()、WeakSet()
  • Promise.all()、Promise.race()

十一、Symbol

​ ES6引入了一种新的原始数据类型Symbol,表示独一无二(互不等价)的值。Symbol出现之前,我们会经常遇到多个不相同的库操作的DOM属性相同,导致第三方库无法正常运行。Symbol解决了“对象属性名都是字符串、数字,这容易造成属性名的冲突”的问题。

Symbol([description]) // 并不是构造函数,不能使用new

​ 很多开发者会误认为Symbol值与我们为其设定的描述有关,其实这个描述仅仅起到了描述的作用,而不会对Symbol值本身起到任何的改变作用。

let s = Symbol();
typeof s;   // "symbol"

const a = Symbol('123');
const b = Symbol('123');
console.log(a == b); // false

Symbol是一种值类型而非引用类型。其意味着将Symbol值作为函数形式传递时,将会进行复制值传递而非引用传递。同时需要注意:Symbol作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()Object.getOwnPropertyNames()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。

var obj = {};
var a = Symbol('a');
var b = Symbol.for('b');

obj[a] = 'Hello';
obj[b] = 'World';

var objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols; // [Symbol(a), Symbol(b)]

Symbol.for()接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

const a = Symbol.for('123');
const b = Symbol.for('123');
console.log(a === b); // true

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

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

常用Symbol值

(1)Symbol.iterator

@@iterator用于为对象定义一个方法并返回一个属于所对应对象的迭代器,该迭代器会被for-of语句使用。

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
class RangeIterator {
    constructor(start, stop) {
        this.start = start;
        this.stop = stop;
    }

    [Symbol.iterator]() {
        return this;
    }

    next() {
        if(this.start < this.stop) {
            this.start++;
            return {done: false, value: this.start};
        }else {
            return {done: true, value: undefined};
        }
    }
}

for(let index of new RangeIterator(0, 3)){
    console.log(index); // 1 2 3
}

(2)Symbol.hasInstance

对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。

class Even {
    static [Symbol.hasInstance](obj) {
        return Number(obj) % 2 === 0;
    }
}
1 instanceof Even; // false
2 instanceof Even; // true

(3)Symbol.match

对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。

"ligang".match(/g/) // ["g", index: 2, input: "ligang"]
// 等价于
(/g/)[Symbol.match]("ligang") // ["g", index: 2, input: "ligang"]
class Matcher {
    constructor(value){
        this.value = value;
    }
     [Symbol.match](str) {
        return this.value.indexOf(str);
    }
}
'g'.match(new Matcher('ligang')); // 2

(4)Symbol.replace

对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。

String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)

(5)Symbol.search

对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。

String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)

(6)Symbol.split

对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。

String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)

十二、Proxy

Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,属于一种“元编程”。(注意,ES5无法模拟该特性)。

元编程重点在于:在一个程序的内容、运行环境、配置等都不做任何修改的情况下,可以通过其他程序对其进行读取或修改。

Getter/Setter

ES5中,可以通过Getter/Setter定义其运行的逻辑或将要返回的值。

var obj = {
    _name: '',
    prefix: '',
    get name() {
        return this.prefix + this._name;
    },
    set name(val) {
        this._name = val;
    }
};

obj.name = 'ligang';
console.log(obj.name); // 'ligang'
obj.prefix = 'hello ';
console.log(obj.name); // 'hello ligang'

Getter/Setter不能对变量的所有行为进行拦截,Proxy提供了这样的能力。

Proxy

Proxy并不是以语法形成使用,而是一种包装器的形式使用。

Syntax: new Proxy(target, handler)

  • target参数表示所要拦截的目标对象
  • handler参数也是一个对象,用来定制拦截行为
属性键(监听参数)监听内容
has(target, propKey)监听in语句的使用
get(target, propKey, receiver)监听目标对象的属性读取
set(target, propKey, value, receiver)监听目标对象的属性赋值
deleteProperty(target, propKey)监听delete语句对目标对象的删除属性行为
ownKeys(target)监听Object.getOwnPropertyNames()的读取
apply(target, object, args)监听目标函数的调用行为
construct(target, args)监听目标构造函数利用new而生成实例的行为
getPrototypeOf(target)监听Object.getPrototypeOf()的读取
setPrototypeOf(target, proto)监听Object.setPrototypeOf()的调用
isExtensible(target)监听Object.isExtensible()的读取
preventExtensions(target)监听Object.preventExtensions()的读取
getOwnPropertyDescriptor(target, propKey)监听Object.getOwnPropertyDescriptor()的读取
defineProperty(target, propKey)监听Object.defineProperty()的调用
let obj = {
    foo: 1,
    bar: 2
};

(1)has(target, propKey)

  • 当目标对象被Object.preventExtensions()禁用,该监听方法不能返回false;
  • 当属性的configurable配置是false时,该监听方法不能返回false。
let p = new Proxy(obj, {
    has(target, propKey) {
        console.log(`检查属性${propKey}是否在目标对象中`);
        return propKey in target;
    }
});
'foo' in p;

Object.preventExtensions(obj);
Object.preventExtensions(obj);
let p2 = new Proxy(obj, {
    has(target, propKey) {
        console.log(`检查属性${propKey}是否在目标对象中`);
        return false;
    }
});
console.log('foo' in p2); // TypeError: 'has' on proxy: trap returned falsish for property 'foo' but the proxy target is not extensible

(2)get(target, propKey, receiver)

当目标对象被读取的属性的configurable和writable属性为false时,监听方法最后返回值必须与目标对象的原属性值一直。

(3)construct(target, args)

该监听方法所返回值必须是一个对象,否则会抛出一个TypeError错误。

解除Proxy对象

Proxy.revocable方法返回一个可取消的 Proxy 实例。

let {proxy, revoke} = Proxy.revocable(target, handler);
  • proxy为可解除Proxy对象
  • revoke为解除方法

Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

let target = {
  bar: 1
};
let handler = {};

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

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

revoke(); // 当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误
proxy.foo; // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.bar; // TypeError: Cannot perform 'get' on a proxy that has been revoked

Proxy使用场景

(1)对象属性自动填充

const obj = {};
obj.foo.name = 'foo'; // Uncaught TypeError: Cannot set property 'name' of undefined

const p = new Proxy(obj, {
    get(target, propKey, receiver) {
        if(!(propKey in target)) {
            target[propKey] = obj; // 注意,obj为空
        }
        return target[propKey];
    }
});
p.bar.name = 'bar';

(2)只读视图

const NOPE = () => {
    throw new Error('Cannot modify the readonly data');
};

function readonly(data) {
    return new Proxy(data, {
        set: NOPE,
        deleteProperty: NOPE,
        setPrototypeOf: NOPE,
        preventExtensions: NOPE,
        defineProperty: NOPE
    })
}
const data = {a: 1};
const readonlyData = readonly(data);

readonlyData.a = 2; // Error: Cannot modify the readonly data
delete readonlyData.a; // Error: Cannot modify the readonly data

(3)入侵式测试框架

通过Proxy在定义方法和逻辑代码之间建立一个隔离层。

import api from './api'

/**
 * 对目标api进行包装
 */
export default hook(api, {
  methodName(fn) {
    return function(...args) {
      console.time('methodName');
      return fn(...args).then((...args) => {
        console.timeEnd('methodName');
        return Promise.resolve(...args);
      });
    }
  }
})

/**
 * 对目标对象的get行为进行监听和干涉
 */
function hook(obj, cases) {
  return new Proxy(obj, {
    get(target, prop) {
      if((prop in cases) && (typeof target[prop] === 'function')) {
        const fn = target[prop];
        return cases[prop](fn);
      }
      return target[prop];
    }
  })
}

十三、 Reflect

Reflect对象的设计目的有这样几个:

  • 将Object对象的一些明显属于语言层面的方法,放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。
  • 更加有用的返回值,修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
  • 函数操作,让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。
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);
  }
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋飛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值