新的ES 6 (Harmony)引入了新的Set对象。Set使用的恒等算法类似于===运算符,所以不太适合比较对象:
var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]
如何自定义相等的集合对象,以做深度对象比较?有没有类似Java = (Object)的东西?
新的ES 6 (Harmony)引入了新的Set对象。Set使用的恒等算法类似于===运算符,所以不太适合比较对象:
var set = new Set();
set.add({a:1});
set.add({a:1});
console.log([...set.values()]); // Array [ Object, Object ]
如何自定义相等的集合对象,以做深度对象比较?有没有类似Java = (Object)的东西?
当前回答
对于TypedArray作为Set/Map键的特殊但常见的情况,使用一种很好的字符串化方法
const key = String.fromCharCode(...new Uint16Array(myArray.buffer));
它生成可以轻松转换回去的最短的惟一字符串。然而,对于低代理和高代理的显示,这并不总是一个有效的UTF-16字符串。Set和Map似乎忽略了代理有效性。 在Firefox和Chrome中,扩展操作符执行得比较慢。如果你的myArray有固定的大小,当你写的时候执行得更快:
const a = new Uint16Array(myArray.buffer); // here: myArray = Uint32Array(2) = 8 bytes
const key = String.fromCharCode(a[0],a[1],a[2],a[3]); // 8 bytes too
这种键构建方法最有价值的优点可能是:它适用于Float32Array和Float64Array,没有任何舍入副作用。注意+0和-0是不同的。无穷大也是一样的。静默nan也一样。信号nan根据它们的信号而不同(在普通JavaScript中从未见过)。
其他回答
正如其他人所说,目前还没有本地方法可以做到这一点。 但是如果你想用你的自定义比较器来区分一个数组,你可以尝试用reduce方法来做。
function distinct(array, equal) {
// No need to convert it to a Set object since it may give you a wrong signal that the set can work with your objects.
return array.reduce((p, c) => {
p.findIndex((element) => equal(element, c)) > -1 || p.push(c);
return p;
}, []);
}
// You can call this method like below,
const users = distinct(
[
{id: 1, name: "kevin"},
{id: 2, name: "sean"},
{id: 1, name: "jerry"}
],
(a, b) => a.id === b.id
);
...
直接比较它们似乎是不可能的,但是JSON。如果键刚刚排序,Stringify就可以工作。正如我在评论中指出的那样
JSON。stringify({a:1, b:2}) !== JSON。stringify ({2,: 1});
但我们可以用自定义stringify方法来解决这个问题。首先,我们编写方法
函数定义把
Object.prototype.stringifySorted = function(){
let oldObj = this;
let obj = (oldObj.length || oldObj.length === 0) ? [] : {};
for (let key of Object.keys(this).sort((a, b) => a.localeCompare(b))) {
let type = typeof (oldObj[key])
if (type === 'object') {
obj[key] = oldObj[key].stringifySorted();
} else {
obj[key] = oldObj[key];
}
}
return JSON.stringify(obj);
}
一组
现在我们使用Set。但是我们使用的是字符串集合而不是对象
let set = new Set()
set.add({a:1, b:2}.stringifySorted());
set.has({b:2, a:1}.stringifySorted());
// returns true
获取所有的值
在创建集合并添加值之后,我们可以通过
let iterator = set.values();
let done = false;
while (!done) {
let val = iterator.next();
if (!done) {
console.log(val.value);
}
done = val.done;
}
这里有一个链接,所有这些都在一个文件中 http://tpcg.io/FnJg2i
对于Typescript用户,其他人(尤其是czerny)的答案可以概括为一个良好的类型安全且可重用的基类:
/**
* Map that stringifies the key objects in order to leverage
* the javascript native Map and preserve key uniqueness.
*/
abstract class StringifyingMap<K, V> {
private map = new Map<string, V>();
private keyMap = new Map<string, K>();
has(key: K): boolean {
let keyString = this.stringifyKey(key);
return this.map.has(keyString);
}
get(key: K): V {
let keyString = this.stringifyKey(key);
return this.map.get(keyString);
}
set(key: K, value: V): StringifyingMap<K, V> {
let keyString = this.stringifyKey(key);
this.map.set(keyString, value);
this.keyMap.set(keyString, key);
return this;
}
/**
* Puts new key/value if key is absent.
* @param key key
* @param defaultValue default value factory
*/
putIfAbsent(key: K, defaultValue: () => V): boolean {
if (!this.has(key)) {
let value = defaultValue();
this.set(key, value);
return true;
}
return false;
}
keys(): IterableIterator<K> {
return this.keyMap.values();
}
keyList(): K[] {
return [...this.keys()];
}
delete(key: K): boolean {
let keyString = this.stringifyKey(key);
let flag = this.map.delete(keyString);
this.keyMap.delete(keyString);
return flag;
}
clear(): void {
this.map.clear();
this.keyMap.clear();
}
size(): number {
return this.map.size;
}
/**
* Turns the `key` object to a primitive `string` for the underlying `Map`
* @param key key to be stringified
*/
protected abstract stringifyKey(key: K): string;
}
示例实现很简单:重写stringifyKey方法。在我的情况下,我stringify一些uri属性。
class MyMap extends StringifyingMap<MyKey, MyValue> {
protected stringifyKey(key: MyKey): string {
return key.uri.toString();
}
}
示例用法是,如果这是一个常规Map<K, V>。
const key1 = new MyKey(1);
const value1 = new MyValue(1);
const value2 = new MyValue(2);
const myMap = new MyMap();
myMap.set(key1, value1);
myMap.set(key1, value2); // native Map would put another key/value pair
myMap.size(); // returns 1, not 2
也许你可以尝试使用JSON.stringify()来进行深度对象比较。
例如:
Const arr = [ {名称:“a”,值:10}, {名称:“a”,值:20}, {名称:“a”,值:20}, {名称:“b”,价值:30}, {名称:“b”,价值:40}, {名称:“b”,价值:40} ]; const names = new Set(); Const result = arr。name .has(JSON.stringify(item)) ?names.add(JSON.stringify(item)): false); console.log(结果);
正如在jfriend00的回答中提到的,平等关系的定制可能是不可能的。
下面的代码给出了一个计算效率高(但内存消耗大)的解决方案:
class GeneralSet {
constructor() {
this.map = new Map();
this[Symbol.iterator] = this.values;
}
add(item) {
this.map.set(item.toIdString(), item);
}
values() {
return this.map.values();
}
delete(item) {
return this.map.delete(item.toIdString());
}
// ...
}
每个插入的元素都必须实现返回字符串的toIdString()方法。当且仅当两个对象的toIdString方法返回相同的值时,才认为它们相等。