ECMAScript 6 入门笔记(七)Symbol,set和map

来源:互联网 发布:linux 剪辑 编辑:程序博客网 时间:2024/06/15 10:28

ECMAScript 6 入门原文 – 阮一峰
ECMAScript 6 入门笔记(一)let,const,解构
ECMAScript 6 入门笔记(二)String,RegExp
ECMAScript 6 入门笔记(三)数值,Array
ECMAScript 6 入门笔记(四)函数,对象

ECMAScript 6 入门笔记(五)异步promise,Generator,async
ECMAScript 6 入门笔记(六)Class
ECMAScript 6 入门笔记(七)Symbol,set和map
ECMAScript 6 入门笔记(八)Proxy,Reflect

Symbol

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

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

注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

const obj = {  toString() {    return 'abc';  }};const sym = Symbol(obj);sym // Symbol(abc)// 没有参数的情况var s1 = Symbol();var s2 = Symbol();s1 === s2 // false// 有参数的情况var s1 = Symbol('foo');var s2 = Symbol('foo');s1 === s2 // falsevar sym = Symbol('My symbol');"your symbol is " + sym// TypeError: can't convert symbol to string`your symbol is ${sym}`// TypeError: can't convert symbol to stringvar sym = Symbol();Boolean(sym) // true!sym  // falseif (sym) {  // ...}Number(sym) // TypeErrorsym + 2 // TypeError

作为属性名的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] // undefineda['mySymbol'] // "Hello!"

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

var obj = {};var a = Symbol('a');var b = Symbol('b');obj[a] = 'hello';obj[b] = 'world';var objectSymbols = Object.getOwnPropertySymbols(obj);objectSymbols   //[Symbol(a),Symbol(b)];

for…in和Object.getOwnPropertyNames方法进行对比的例子。

var obj = {};var foo = Symbol("foo");Object.defineProperty(obj, foo, {  value: "foobar",});for (var i in obj) {  console.log(i); // 无输出}Object.getOwnPropertyNames(obj)// []Object.getOwnPropertySymbols(obj)// [Symbol(foo)]

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

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

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

Foo[Symbol.hasInstance](foo)。class MyClass {  [Symbol.hasInstance](foo) {    return foo instanceof Array;  }}[1, 2, 3] instanceof new MyClass() // true

Symbol.isConcatSpreadable
对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。

let arr1 = ['c', 'd'];['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']arr1[Symbol.isConcatSpreadable] // undefinedlet arr2 = ['c', 'd'];arr2[Symbol.isConcatSpreadable] = false;['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']

对于一个类来说,Symbol.isConcatSpreadable属性必须写成实例的属性。

class A1 extends Array {  constructor(args) {    super(args);    this[Symbol.isConcatSpreadable] = true;  }}class A2 extends Array {  constructor(args) {    super(args);    this[Symbol.isConcatSpreadable] = false;  }}let a1 = new A1();a1[0] = 3;a1[1] = 4;let a2 = new A2();a2[0] = 5;a2[1] = 6;[1, 2].concat(a1).concat(a2)// [1, 2, 3, 4, [5, 6]]

SET和Map数据结构

ES6提供了新的数据结构Set,他类似于数组,但是成员的值都是唯一的,没有重复的值。
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 函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。

// 例一const set = new Set([1, 2, 3, 4, 4]);[...set]// [1, 2, 3, 4]// 例二const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);items.size // 5// 例三function divs () {  return [...document.querySelectorAll('div')];}const set = new Set(divs());set.size // 56// 类似于divs().forEach(div => set.add(div));set.size // 56

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 // 2s.has(1) // trues.has(2) // trues.has(3) // falses.delete(2);s.has(2) // false

Array.from方法可以将 Set 结构转为数组。

const items = new Set([1, 2, 3, 4, 5]);const array = Array.from(items);这就提供了去除数组重复成员的另一种方法。function dedupe(array) {  return Array.from(new Set(array));}dedupe([1, 1, 2, 3]) // [1, 2, 3]

遍历操作
Set 结构的实例有四个遍历方法,可以用于遍历成员。

keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员
需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用。

(1)keys(),values(),entries()

keys方法、values方法、entries方法返回的都是遍历器对象(详见《Iterator 对象》一章)。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

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”]
上面代码中,entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。

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
(2)forEach()

Set结构的实例的forEach方法,用于对每个成员执行某种操作,没有返回值。

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

(3)遍历的应用
扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构。
let set = new Set([‘red’, ‘green’, ‘blue’]);
let arr = […set];
// [‘red’, ‘green’, ‘blue’]
扩展运算符和 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}

因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。

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}// 差集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上面代码提供了两种方法,直接在遍历操作中改变原来的 Set 结构。

WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。首先,WeakSet 的成员只能是对象,而不能是其他类型的值。

WeakSet是一个构造函数,可以使用new命令,来创建WeakSet数据结构
const a = [[1,2],[3,4]];
const ws = new WeakSet(a);
// WeakSet {[1,2],[3,4]}

WeakSet结构有以下三个方法
WeakSet.prototype.add(value):向weakset实例添加一个新成员。
WeakSet.prototype.delete(value):清除WeakSet实例的指定成员。
WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例中

const ws = new WeakSet();const obj = {};const foo = {};ws.add(window);ws.add(obj);ws.has(window);  //truews.has(foo);     //falsews.delete(window);ws.has(window);  //falseweakset没有size属性,没有办法遍历它的变量ws.size  //undefinedws.forEach //undefined

weakset不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员那就取不到了.weakset的一个用处,是储存dom节点,而不用担心这些节点从文档移除时,内存泄漏(WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。)

MAP
JavaScript的对象(object),本质上是键值对的集合,但是传统上只能用字符串当作键,带来很大的限制
const data = {};
const element = document.getElementById(“myDiv”);

data[element] = ‘metadata’;
data[‘[object HTMLDivElement]’] //metadata
上面是es5的代码

es6提供了Map数据结构,他类似对象,也是键值对的集合,但是键的范围不限于字符串,各种类型都能用来做键

const m = new Map(); const o = {p:'hello world'}m.set(o,'content');m.get(o);  //contentm.has(o);  //truem.delete(o);  //truem.has(o);  //falseMap添加成员,作为构造函数,Map也可以接收一个数组作为参数.const map = new Map([    ['name','张三'],    ['title','Auther']]);map.size  //2map.has('name')  //truemap.get('name')  //张三

任何具有Iterator接口的数据结构

const set = new Set([    ['foo',1],    ['bar',2]]);cosnt m1 = new Map(set);m1.get('foo');  //1const m2 = new Map([['bar',3]]);const m3 = new Map(m2);m3.get('baz');  //3const map = new Map();map.set(['a'],555);map.get(['a']);const k1 = ['a'];const k2 = ['a'];map.set(k1,111).set(k2,222);map.get(k1);  //111map.get(k2);  //222

map的键实际上是跟内存地址绑定的,只要内存地址不一样,就是为两个值,解决了同名属性碰撞的问题

实例的属性和操作方法
(1)size属性
const map = new Map();
map.set(‘foo’,true);
map.set(‘bar’,false);
map.size; //2

(2)set(key,value)
const m = new Map();
m.set(‘edition’,6);
m.set(262,’standrad’);
m.set(undefined,’nah’);

(3)get(key)
m.get(‘edition’);

(4)has(key)
m.has(‘edition’);

(5)delete(key)
m.delete(‘edition’);

(6)clear
清除所有成员没有返回值
let map = new Map();
map.set(‘foo’,true);
map.set(‘bar’,false);

map.size; //2
map.clear();
map.size; //0

遍历方式
Map结构原生提供三个遍历器生成函数和一个遍历方法
keys 返回键名的遍历器
values 返回键值的遍历器
entries 返回所有成员的遍历器
forEach 遍历Map的所有成员

const map = new Map([    ['F','no'],    ['T','yes']]);for(let key of map.keys()){    console.log(key);}// "F" "T"for(let value of map.values()){    console.log(value);}for(let item of map.entries()){    console.log(item[0],item[1]);}// "F" "no"// "T" "yes"for(let [key,value] of map.entries()){    console.log(key,value);}for(let [key,value] of map){    console.log(key,value);}Map结构转换为数组结构,比较快速的方法是使用扩展运算符const map = new Map([    [1,'one'],    [2,'two'],    [3,'three']]);[...map.keys()]     //[1,2,3];[...map.valus()]    //['one','two','three'];[...map.entries()]  //[[1,'one'],[2,'two'],[3,'three']];[...map]            //[[1,'one'],[2,'two'],[3,'three']];

与其他数据结构的相互转换
(1)Map转换为数组
const myMap = new Map().set(true,7).set({foo,3},[‘abc’]);
[…myMap]; //[[true,7],[{foo:3},[‘abc’]]];

(2)数组转换为Map
new Map([
[true,7],
[{foo:3},[‘abc’]]
]);

(3)Map转为对象
如果所有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}

(4)对象转为Map
function objToStrMap(obj){
let strMap = new Map();
for(let k of Object.keys(obj)){
strMap.set(k,obj[k]);
}
return strMap;
}

(5)Map转换为JSON
Map转为JSON要区分两种情况.一种情况是,Map的键名都是字符串,这时可以选择转换为对象JSON
function strMapToJson(strMap){
return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set(true, 7).set({foo: 3}, [‘abc’]);
mapToArrayJson(myMap)
// ‘[[true,7],[{“foo”:3},[“abc”]]]’

(6)JSON转为Map
function jsonToStrMap(jsonstring){
return JSON.parse(jsonStr);
}

jsonToStrMap(‘{“yes”: true, “no”: false}’)
// Map {‘yes’ => true, ‘no’ => false}

4.WeakMap
含义WeakMap结构与Map结构类似,也是用于生成键值对
const wm1 = new WeakMap();
const key = {foo:1};
wm1.set(key,2);
wm1.get(key);

0 0