ES6--Set、Map、Symbol、Proxy及Reflect_奋飛的博客-程序员信息网

技术标签: Proxy  Set  Symbol  ES6  javascript  

九、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);
  }
});
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ligang2585116/article/details/70553094

智能推荐

打通链上链下,构建全链条可信价值生态—长安链预言机_长安链 有状态树吗_长安链开源社区的博客-程序员信息网

长安链·预言机v1.0.0_alpha版本是⼀种中⼼化的可分布式部署的预⾔机,为⻓安链获取链外数据提供了⼀个桥梁,可以将现实世界的数据准确无误的写入到区块链上,应用场景非常广泛。

java byte 与 binary 转换_binary byte_天空灬之城的博客-程序员信息网

在写通讯程序时,有时需要解析二进制数据流,可能会遇到java数值类型与二进制byte数组的类型转换问题。因此,笔者提供下面代码实例,供大家需要时参考。importjava.io.*;/***java数值类型与二进制类型相互转换*@authorlijun**/publicclassMyTest{/***int32转换为二进制(4个字节)*@par

如何免费安装并激活序列号iMazing软件_imazing 序列号_CoCo玛奇朵的博客-程序员信息网

iMazing是一款能够快速传输和保存音乐、文件、照片以及进行备份的iOS设备管理软件,同时支持Mac和Windows两大系统,功能强大,便于使用,下面小编就通过一则简单的教程,教大家如何下载安装并激活iMazing。先行声明,本教程是在Windows10操作系统上,使用iMazing 2版本为大家进行演示讲解。一、下载安装首先进入iMazing中文网站,然后进入到下载页选择iMazing for Windows版本进行下载,因为小编用的是Windows系统的电脑。下载完成以后,可以得到如图1的软件

PHP之MySQL笔记4_-恰饭第一名-的博客-程序员信息网

1.多表查询1.1 内连接规则:返回两个表的公共记录语法:-- 语法一select * from 表1 inner join 表2 on 表1.公共字段=表2.公共字段-- 语法二select * from 表1,表2 where 表1.公共字段=表2.公共字段例题:-- inner joinmysql&gt; select * from stuinfo inner join stumarks on stuinfo.stuno=stumarks.stuno;+--------+--

Godot教程中文版-脚本(Scripting)_godotscript_dengchunfeng的博客-程序员信息网

脚本 (Scripting) https://github.com/okamstudio/godot/wiki/tutorial_scripting介绍很多人都会谈及到一些可以允许开发者不用coding就能制作出视频游戏的工具。对于很多独立开发者来讲,不用怎么学习编程就可以制作游戏是一个梦想。这个需求已经存在很长一段时间了,甚至在一些公司内,很多开发者都希望可以更多的把控游戏流程。

让你明明白白的使用RecyclerView——SnapHelper详解_Android心路历程的博客-程序员信息网

简介RecyclerView在24.2.0版本中新增了SnapHelper这个辅助类,用于辅助RecyclerView在滚动结束时将Item对齐到某个位置。特别是列表横向滑动时,很多时候不会让列表滑到任意位置,而是会有一定的规则限制,这时候就可以通过SnapHelper来定义对齐规则了。SnapHelper是一个抽象类,官方提供了一个LinearSnapHelper的子类,可以让Recyc...

随便推点

SQL Server多表关联Update_糖老鸦的博客-程序员信息网

示例:update aaa set aaa.name = bbb.namefrom user_01 aaa left join user_02 bbb on aaa.code = bbb.codewhere bbb.name is not null;注意:只是要注意的一点是,如果在from子句中给表起了别名的话,需要在update语句中也使用该别名,否则可能会报错的。...

allure的安装及配置_m0_49225959的博客-程序员信息网

allure的安装及配置allure是一款轻量级并且非常灵活的开源测试报告框架。 它支持绝大多数测试框架,它简单易用,易于集成。一、allure的下载·下载网址(allure官网):http://allure.qatools.ru/·点击download按钮进入下载界面·选择如下图所示的文件,并下载二、allure的安装·将刚才下载的文件解压到你用于存放它的位置(不建议放置于主机盘(一般是C:\),在这里我存放在了D:\allure)中·解压完成后,进入文件夹bin中

Qt 调用 C语言函数的一种解决方案 C++ 调用 C 语言函数_qt调用c语言函数_通哥哥123的博客-程序员信息网

解决方法:将C语言函数打包成静态链接库比如我们的课程设计需要自己用100M存储区模拟一个文件系统,那么我首先用纯C语言写出create、delete、write、read等等系统调用,然后用Qt写出一个界面来,界面操作均需要调用C 语言函数。首先,写好C语言的函数,如图:然后打包成静态连接库,方法是先打包成.o文件,然后再打包成.a的静态链接库文件,具体方法百度。结果如图:此时...

Vue中让文本框获取焦点_this.$refs.txt.focus_suiersuier的博客-程序员信息网

Vue写的页面中使用指令获取焦点后会出现一种显现,不论点击哪里都会让那一个input获取焦点,所以不要使用指令来写使用如下代码放到你要获取焦点的地方即可获取焦点,其中searchTxt是要获取焦点的ref的名称this.$nextTick((x)=&gt;{ //正确写法 this.$refs.searchTxt.focus();})页面写法js中方法页...

什么是css选择器?css3中5种常见的基本选择器(代码实例)_css3的基础选择器有哪些?_程序员的生活1的博客-程序员信息网

本章我们给大家讲解一下什么是css选择器?css3中5种常见的基本选择器。有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。一:什么是css选择器?CSS是一种用于屏幕上渲染html,xml等一种语言,CSS主要是在相应的元素中应用样式,来渲染相对应用的元素,那么这样我们选择相应的元素就很重要了,如何选择对应的元素,此时就需要我们所说的选择器。在 CSS 中,选择器是一种模式,用于选择需要添加样式的元素。选择器主要是用来确定html的树形结构中的DOM元素节点。二:css3中5.

史上最详细最简单最全WSL,win10安装windows的ubuntu子系统,Linux详细配置_exit(-1)的博客-程序员信息网

一、打开linux子系统功能打开控制面板2. 勾选上适用于Linux的windows子系统,然后重启 二、下载Ubuntu打开微软商店搜索Linux下载 ubuntu400多M,下载完,直接启动, 这里装的是20.04启动进入这个界面,等待一会,然后让你输入一个用户名(创建用户, 假设为user),再输入俩次密码,用户创建成功修改root的密码sudo passwd root测试一下:su - root、su - user 查看是否切换成功三、配置Ubuntu安装

推荐文章

热门文章

相关标签