我在使用JavaScript。我想存储一个唯一,无序字符串值的列表,其中包含以下属性:
我真正想要的是一套。有关在JavaScript中模仿集合的最佳方法的任何建议吗?
这个question recommends using an Object,键存储属性,值都设置为true:这是一种明智的方法吗?
答案 0 :(得分:258)
如果您在支持ES6的环境中进行编程(例如node.js,具有您需要的ES6功能的特定浏览器或为您的环境转换ES6代码),那么您可以使用Set
object built into ES6。它具有非常好的功能,可以在您的环境中使用。
对于ES5环境中的许多简单事物,使用Object非常有效。如果obj
是您的对象而A
是一个具有您要在集合中操作的值的变量,那么您可以执行以下操作:
初始化代码:
// create empty object
var obj = {};
// or create an object with some items already in it
var obj = {"1":true, "2":true, "3":true, "9":true};
问题1:列表中有A
:
if (A in obj) {
// put code here
}
问题2:如果有,请从列表中删除“A”:
delete obj[A];
问题3:如果列表中尚未添加“A”
obj[A] = true;
为了完整起见,对A
是否在列表中的测试更加安全:
if (Object.prototype.hasOwnProperty.call(obj, A))
// put code here
}
因为基础对象上的内置方法和/或属性之间可能存在冲突,例如constructor
属性。
ES6上的补充工具栏 ECMAScript 6 的当前工作版本或称为ES 2015的某些东西有一个内置的Set对象。它现在在一些浏览器中实现。由于浏览器可用性会随着时间的推移而变化,因此您可以查看this ES6 compatibility table中Set
的行,以查看浏览器可用性的当前状态。
内置Set对象的一个优点是它不会像对象那样强制将所有键强制转换为字符串,因此您可以同时使用5和“5”作为单独的键。并且,您甚至可以直接在集合中使用对象而无需字符串转换。这里的an article描述了Set对象的一些功能和MDN's documentation。
我现在已经为ES6设置对象编写了一个polyfill,所以你现在可以开始使用它,如果浏览器支持它,它会自动推迟到内置的set对象。这样做的好处是,您正在编写ES6兼容代码,这些代码可以一直回到IE7。但是,有一些缺点。 ES6集接口利用了ES6迭代器,因此您可以执行for (item of mySet)
之类的操作,它会自动为您迭代。但是,这种语言功能无法通过polyfill实现。您仍然可以在不使用新的ES6语言功能的情况下迭代ES6集,但坦率地说,如果没有新的语言功能,它就不如我在下面包含的其他设置界面那么方便。
在查看两者后,您可以决定哪一个最适合您。 ES6 set polyfill位于:https://github.com/jfriend00/ES6-Set。
仅供参考,在我自己的测试中,我注意到Firefox v29 Set实现并未完全了解当前的规范草案。例如,您不能链接.add()
方法调用,例如spec描述和我的polyfill支持。这可能是运动规范的问题,因为它还没有最终确定。
预建集对象:如果您想要一个已构建的对象,该对象具有可在任何浏览器中使用的集合上操作的方法,则可以使用一系列不同的预构建对象实现不同类型的集合。有一个miniSet,它是实现set对象基础的小代码。它还有一个功能更丰富的set对象和几个派生,包括Dictionary(让你存储/检索每个键的值)和一个ObjectSet(让你保留一组对象 - JS对象或DOM对象,你要么提供它们为每个生成唯一键的函数或ObjectSet将为您生成密钥。
以下是miniSet代码的副本(最新代码为here on github)。
"use strict";
//-------------------------------------------
// Simple implementation of a Set in javascript
//
// Supports any element type that can uniquely be identified
// with its string conversion (e.g. toString() operator).
// This includes strings, numbers, dates, etc...
// It does not include objects or arrays though
// one could implement a toString() operator
// on an object that would uniquely identify
// the object.
//
// Uses a javascript object to hold the Set
//
// This is a subset of the Set object designed to be smaller and faster, but
// not as extensible. This implementation should not be mixed with the Set object
// as in don't pass a miniSet to a Set constructor or vice versa. Both can exist and be
// used separately in the same project, though if you want the features of the other
// sets, then you should probably just include them and not include miniSet as it's
// really designed for someone who just wants the smallest amount of code to get
// a Set interface.
//
// s.add(key) // adds a key to the Set (if it doesn't already exist)
// s.add(key1, key2, key3) // adds multiple keys
// s.add([key1, key2, key3]) // adds multiple keys
// s.add(otherSet) // adds another Set to this Set
// s.add(arrayLikeObject) // adds anything that a subclass returns true on _isPseudoArray()
// s.remove(key) // removes a key from the Set
// s.remove(["a", "b"]); // removes all keys in the passed in array
// s.remove("a", "b", ["first", "second"]); // removes all keys specified
// s.has(key) // returns true/false if key exists in the Set
// s.isEmpty() // returns true/false for whether Set is empty
// s.keys() // returns an array of keys in the Set
// s.clear() // clears all data from the Set
// s.each(fn) // iterate over all items in the Set (return this for method chaining)
//
// All methods return the object for use in chaining except when the point
// of the method is to return a specific value (such as .keys() or .isEmpty())
//-------------------------------------------
// polyfill for Array.isArray
if(!Array.isArray) {
Array.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
function MiniSet(initialData) {
// Usage:
// new MiniSet()
// new MiniSet(1,2,3,4,5)
// new MiniSet(["1", "2", "3", "4", "5"])
// new MiniSet(otherSet)
// new MiniSet(otherSet1, otherSet2, ...)
this.data = {};
this.add.apply(this, arguments);
}
MiniSet.prototype = {
// usage:
// add(key)
// add([key1, key2, key3])
// add(otherSet)
// add(key1, [key2, key3, key4], otherSet)
// add supports the EXACT same arguments as the constructor
add: function() {
var key;
for (var i = 0; i < arguments.length; i++) {
key = arguments[i];
if (Array.isArray(key)) {
for (var j = 0; j < key.length; j++) {
this.data[key[j]] = key[j];
}
} else if (key instanceof MiniSet) {
var self = this;
key.each(function(val, key) {
self.data[key] = val;
});
} else {
// just a key, so add it
this.data[key] = key;
}
}
return this;
},
// private: to remove a single item
// does not have all the argument flexibility that remove does
_removeItem: function(key) {
delete this.data[key];
},
// usage:
// remove(key)
// remove(key1, key2, key3)
// remove([key1, key2, key3])
remove: function(key) {
// can be one or more args
// each arg can be a string key or an array of string keys
var item;
for (var j = 0; j < arguments.length; j++) {
item = arguments[j];
if (Array.isArray(item)) {
// must be an array of keys
for (var i = 0; i < item.length; i++) {
this._removeItem(item[i]);
}
} else {
this._removeItem(item);
}
}
return this;
},
// returns true/false on whether the key exists
has: function(key) {
return Object.prototype.hasOwnProperty.call(this.data, key);
},
// tells you if the Set is empty or not
isEmpty: function() {
for (var key in this.data) {
if (this.has(key)) {
return false;
}
}
return true;
},
// returns an array of all keys in the Set
// returns the original key (not the string converted form)
keys: function() {
var results = [];
this.each(function(data) {
results.push(data);
});
return results;
},
// clears the Set
clear: function() {
this.data = {};
return this;
},
// iterate over all elements in the Set until callback returns false
// myCallback(key) is the callback form
// If the callback returns false, then the iteration is stopped
// returns the Set to allow method chaining
each: function(fn) {
this.eachReturn(fn);
return this;
},
// iterate all elements until callback returns false
// myCallback(key) is the callback form
// returns false if iteration was stopped
// returns true if iteration completed
eachReturn: function(fn) {
for (var key in this.data) {
if (this.has(key)) {
if (fn.call(this, this.data[key], key) === false) {
return false;
}
}
}
return true;
}
};
MiniSet.prototype.constructor = MiniSet;
答案 1 :(得分:71)
您可以创建一个没有
等属性的对象var set = Object.create(null)
可以作为一个集合,无需使用hasOwnProperty
。
var set = Object.create(null); // create an object with no properties
if (A in set) { // 1. is A in the list
// some code
}
delete set[a]; // 2. delete A from the list if it exists in the list
set[A] = true; // 3. add A to the list if it is not already present
答案 2 :(得分:22)
答案 3 :(得分:14)
在ES6版本的Javascript中,您为set(check compatibility with your browser)内置了类型。
var numbers = new Set([1, 2, 4]); // Set {1, 2, 4}
要添加元素到集合,您只需使用.add()
O(1)
运行numbers.add(4); // Set {1, 2, 4}
numbers.add(6); // Set {1, 2, 4, 6}
并添加要设置的元素(如果它不存在)或者没有,如果它已经存在。您可以在那里添加任何类型的元素(数组,字符串,数字)
.size
要检查集合中的元素数量,您只需使用O(1)
即可。也在numbers.size; // 4
.delete()
要从集合中移除元素,请使用O(1)
。如果值存在(并且已被删除),则返回true;如果值不存在,则返回false。也在numbers.delete(2); // true
numbers.delete(2); // false
中运行。
.has()
要在集合use O(1)
中检查元素是否存在,如果元素在集合中则返回true,否则返回false。也在numbers.has(3); // false
numbers.has(1); // true
中运行。
numbers.clear();
除了您想要的方法之外,还有一些方法:
numbers.forEach(callback);
只会移除集合中的所有元素numbers.entries();
以插入顺序迭代集合的值numbers.keys();
创建所有值的迭代器numbers.values()
返回集合中与{{1}} 还有一个Weakset,它只允许添加对象类型的值。
答案 4 :(得分:9)
我已经启动了一个集合的实现,目前在数字和字符串方面效果非常好。我的主要关注点是差异操作,所以我试图尽可能高效。叉子和代码评论是受欢迎的!
答案 5 :(得分:8)
我刚刚注意到d3.js库具有集合,映射和其他数据结构的实现。 我不能争论他们的效率,但从它是一个受欢迎的图书馆来判断它必须是你所需要的。
文档为here
为方便起见,我从链接中复制(前3个功能是感兴趣的)
构造一个新集。如果指定了array,则将给定的字符串值数组添加到返回的集合中。
当且仅当此集合具有指定值字符串的条目时才返回true。
将指定的值字符串添加到此集合中。
如果集合包含指定的值字符串,则将其删除并返回true。否则,此方法不执行任何操作并返回false。
返回此set中字符串值的数组。返回值的顺序是任意的。可以用作计算一组字符串的唯一值的便捷方式。例如:
d3.set([“foo”,“bar”,“foo”,“baz”])。values(); //“foo”,“bar”,“baz”
为此集合中的每个值调用指定的函数,并将该值作为参数传递。函数的这个上下文就是这个集合。返回undefined。迭代顺序是任意的。
当且仅当此集合的值为零时才返回true。
返回此集合中的值数。
答案 6 :(得分:4)
是的,这是一种明智的方式 - 所有的对象都是(对于这个用例) - 一堆直接访问的键/值。
在添加它之前,您需要检查它是否已经存在,或者如果您只需要指示存在,再次“添加”它实际上不会改变任何内容,它只是再次将它设置在对象上。 / p>