Set基本使用
初始化
Set 本身是一个构造函数,用来生成 Set 数据结构
var set = new Set();
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
let set = new Set([1, 2, 3, 4, 4]);
console.log(set); // Set(4) {1, 2, 3, 4}
set = new Set(document.querySelectorAll('div'));
set.size // 56
set = new Set(new Set([1, 2, 3, 4, 4]));
console.log(set); // Set(4) {1, 2, 3, 4}
属性和方法
属性:
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
操作方法:
Set.prototype.add(value)
:添加某个值,返回 Set 结构本身。Set.prototype.delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.has(value)
:返回一个布尔值,表示该值是否为Set
的成员。Set.prototype.clear()
:清除所有成员,没有返回值。
let set = new Set();
console.log(set.add(1).add(2)); // Set(2) {1, 2}
console.log(set.delete(2)); // true
console.log(set.has(2)); // false
console.log(set.clear()); // undefined
console.log(set.size); // 0
遍历操作
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员
let set = new Set(['a', 'b', 'c']);
for (let pair of set.entries()) {
console.log(pair);
}
// ['a', 'a']
// ['b', 'b']
// ['c', 'c']
for (let key of set.keys()) {
console.log(key);
}
// a
// b
// c
for (let value of set.values()) {
console.log(value);
}
// a
// b
// c
set.forEach((value, key) => console.log(key + ': ' + value));
// a: a
// b: b
// c: c
可以看出Set 类型的 keys() 和 values() 返回的是相同的迭代器,这也意味着在 Set 这种数据结构中键名与键值相同。在 for-of 循环中,Set 集合的默认迭代器是 values() 方法
实现Set
先实现 add、delete、has、clear、forEach 方法
/**
* 模拟实现第一版
*/
function Set(data) {
this.items = [];
this.size = 0;
data &&
data.forEach(function (item) {
this.add(item);
}, this);
}
Set.prototype.add = function (value) {
if (this.items.indexOf(value) == -1) {
this.items.push(value);
++this.size;
}
return this;
};
Set.prototype.has = function (value) {
return this.items.indexOf(value) !== -1;
};
Set.prototype.delete = function (value) {
var index = this.items.indexOf(value);
if (index == -1) return false;
this.items.splice(index, 1);
--this.size;
return true;
};
Set.prototype.clear = function (value) {
this.items = [];
this.size = 0;
};
Set.prototype.forEach = function (callbackFn, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.items.length; i++) {
callbackFn.call(thisArg, this.items[i], this.items[i], this);
}
};
Set.length = 0;
弥补indexOf的缺陷
实际上Set添加多个 NaN会去重
let set = new Set();
set.add(NaN);
set.add(NaN);
console.log(set.size); // 1
但我们模拟的Set添加元素使用的是indexOf
因为indexOf本质上,还是使用 === 来进行比较,所以需要考虑NaN
var s = NaN;
s === NaN; // false
console.log([NaN].indexOf(s)); // -1
所以我们需要对 NaN 这个值进行单独的处理
处理的方式是当判断添加的值是 NaN 时,将其替换为一个独一无二的值,比如说一个很难重复的字符串类似于 @@NaNValue
,当然了,说到独一无二的值,我们也可以直接使用 Symbol,代码如下:
即使用indexOf时判断下:
var NaNSymbol = Symbol("NaN");
var encodeVal = function (value) {
return value !== value ? NaNSymbol : value;
};
var decodeVal = function (value) {
return value === NaNSymbol ? NaN : value;
};
// ...
Set.prototype.add = function (value) {
value = encodeVal(value);
if (this.items.indexOf(value) == -1) {
this.items.push(value);
++this.size;
}
return this;
};
Set.prototype.has = function (value) {
return this.items.indexOf(encodeVal(value)) !== -1;
};
Set.prototype.delete = function (value) {
var index = this.items.indexOf(encodeVal(value));
if (index == -1) return false;
this.items.splice(index, 1);
--this.size;
return true;
};
// ...
实现 keys()、values()、entries()
var NaNSymbol = Symbol("NaN");
var encodeVal = function (value) {
return value !== value ? NaNSymbol : value;
};
var decodeVal = function (value) {
return value === NaNSymbol ? NaN : value;
};
var makeIterator = function (array, iterator) {
var nextIndex = 0;
// new Set(new Set()) 会调用这里
var obj = {
next: function () {
return nextIndex < array.length
? { value: iterator(array[nextIndex++]), done: false }
: { value: void 0, done: true };
},
};
// [...set.keys()] 会调用这里
obj[Symbol.iterator] = function () {
return obj;
};
return obj;
};
function forOf(obj, cb) {
let iterable, result;
if (typeof obj[Symbol.iterator] !== "function")
throw new TypeError(obj + " is not iterable");
if (typeof cb !== "function") throw new TypeError("cb must be callable");
iterable = obj[Symbol.iterator]();
result = iterable.next();
while (!result.done) {
cb(result.value);
result = iterable.next();
}
}
function Set(data) {
this.items = [];
this.size = 0;
data && forOf(data, (item) => {
this.add(item);
});
}
Set.prototype.add = function (value) {
value = encodeVal(value);
if (this.items.indexOf(value) == -1) {
this.items.push(value);
++this.size;
}
return this;
};
Set.prototype.has = function (value) {
return this.items.indexOf(encodeVal(value)) !== -1;
};
Set.prototype.delete = function (value) {
var index = this.items.indexOf(encodeVal(value));
if (index == -1) return false;
this.items.splice(index, 1);
--this.size;
return true;
};
Set.prototype.clear = function (value) {
this.items = [];
this.size = 0;
};
// keys() 和 values() 返回的是相同的迭代器
Set.prototype.values = Set.prototype.keys = function () {
return makeIterator(this.items, function (value) {
return decodeVal(value);
});
};
// 键名与键值相同
Set.prototype.entries = function () {
return makeIterator(this.items, function (value) {
return [decodeVal(value), decodeVal(value)];
});
};
// for of默认迭代器是 values() 方法
Set.prototype[Symbol.iterator] = function () {
return this.values();
};
Set.prototype.forEach = function (callbackFn, thisArg) {
thisArg = thisArg || window;
var iterator = this.entries();
forOf(iterator, (item) => {
callbackFn.call(thisArg, item[1], item[0], this);
});
};
Set.length = 0;
测试:
let set = new Set([1, 2, 3]);
set.add(NaN).add(NaN);
console.log([...set]); // [1, 2, 3, NaN]
console.log([...set.keys()]); // [1, 2, 3, NaN]
console.log([...set.values()]); // [1, 2, 3, NaN]
console.log([...set.entries()]); // [1, 2, 3, NaN]
set.forEach(function(value, key){
console.log(key + ': ' + value);
})
// 1: 1
// 2: 2
// 3: 3
// NaN: NaN
Map基本使用
初始化
Map 本身是一个构造函数,用来生成 Map 数据结构
var map = new Map();
不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)都可以当作Map
构造函数的参数
let map = new Map([
['name', '张三'],
['title', 'Author']
]);
console.log(map); // Map(2) {'name' => '张三', 'title' => 'Author'}
let set = new Set([
['foo', 1],
['bar', 2]
]);
map = new Map(set); // Map(2) {'foo' => 1, 'bar' => 2}
属性和方法
属性:
Map.prototype.constructor
:构造函数,默认就是Map
函数。Map.prototype.size
:返回Map 结构的成员总数
操作方法:
Map.prototype.set(key, value)
:设置键名key
对应的键值为value
,然后返回整个 Map 结构。如果key
已经有值,则键值会被更新,否则就新生成该键。Map.prototype.get(key)
:读取key
对应的键值,如果找不到key
,返回undefined
。Map.prototype.delete(key)
:删除某个键,返回一个布尔值,表示删除是否成功。Map.prototype.has(key)
:返回一个布尔值,表示某个键是否在当前 Map 对象之中。Map.prototype.clear()
:清除所有成员,没有返回值。
var map = new Map([
['name', '张三'],
['title', 'Author']
]);
console.log(map.size); // 2
map.set('age', 20).set('age', 18);
console.log(map.has('name')); // true
console.log(map.get('name')); // "张三"
map.delete('title');
console.log(map.get('title')) // "Author"
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
// Key: name, Value: 张三
// Key: age, Value: 18
遍历操作
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回所有成员的遍历器。Set.prototype.forEach()
:遍历 Map 的所有成员。
let map = new Map()
map.set(0, 'a');
map.set(1, 'b');
map.set(2, 'c');
for (let pair of map.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
for (let key of map.keys()) {
console.log(key);
}
// 0
// 1
// 2
for (let value of map.values()) {
console.log(value);
}
// a
// b
// c
map.forEach(function(value, key, map) {
console.log(key + ': ' + value);
});
// 0: a
// 1: b
// 2: c
可以看出Map 类型与数组类似
实现Map
function dealKey(key) {
if (key === null) {
return "NULL";
} else if (key === undefined) {
return "UNDEFINED";
} else if (
Object.prototype.toString.call(key) === "[object Object]" ||
Object.prototype.toString.call(key) === "[object Array]"
) {
return JSON.stringify(key);
}
return key.toString();
}
var makeIterator = function (o, iterator) {
var keys = Object.keys(o);
var nextIndex = 0;
var obj = {
next: function () {
return nextIndex < keys.length
? { value: iterator(keys[nextIndex], o[keys[nextIndex++]]), done: false }
: { value: void 0, done: true };
},
};
obj[Symbol.iterator] = function () {
return obj;
};
return obj;
};
function forOf(obj, cb) {
let iterable, result;
if (typeof obj[Symbol.iterator] !== "function")
throw new TypeError(obj + " is not iterable");
if (typeof cb !== "function") throw new TypeError("cb must be callable");
iterable = obj[Symbol.iterator]();
result = iterable.next();
while (!result.done) {
cb(result.value);
result = iterable.next();
}
}
function Map(data) {
this.items = {};
this.size = 0;
data && forOf(data, (item) => {
this.set(item[0], item[1]);
});
}
Map.prototype.set = function (key, value) {
if (!this.has(key)) {
++this.size;
}
this.items[dealKey(key)] = value;
return this;
};
Map.prototype.get = function (key) {
return this.items[dealKey(key)];
};
Map.prototype.has = function (key) {
return this.items[dealKey(key)] !== undefined;
};
Map.prototype.delete = function (key) {
if (this.has(key)) {
delete this.items[key];
--this.size;
}
return this;
};
Map.prototype.clear = function () {
this.items = {};
this.size = 0;
};
Map.prototype.keys = function () {
return makeIterator(this.items, function (key, value) {
return key;
});
};
Map.prototype.values = function () {
return makeIterator(this.items, function (key, value) {
return value;
});
};
// 键名与键值相同
Map.prototype.entries = function () {
return makeIterator(this.items, function (key, value) {
return [key, value];
});
};
// for of默认迭代器是 entries() 方法
Map.prototype[Symbol.iterator] = function () {
return this.entries();
};
Map.prototype.forEach = function (callbackFn, thisArg) {
thisArg = thisArg || window;
var iterator = this.entries();
forOf(iterator, (item) => {
callbackFn.call(thisArg, item[1], item[0], this);
});
};
测试下:
let map = new Map()
map.set(0, 'a');
map.set(1, 'b');
map.set(2, 'c');
for (let pair of map.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
for (let key of map.keys()) {
console.log(key);
}
// 0
// 1
// 2
for (let value of map.values()) {
console.log(value);
}
// a
// b
// c
map.forEach(function(value, key, map) {
console.log(key + ': ' + value);
});
// 0: a
// 1: b
// 2: c
参考资料: