我正在使用 JavaScript。我想存储一个 唯一的 、无序的字符串值列表,具有以下属性:
我真正想要的是一套。关于在 JavaScript 中模仿集合的最佳方法有什么建议吗?
如果您在支持 ES6 的环境中编程(例如 node.js,具有您需要的 ES6 功能的特定浏览器或为您的环境转译 ES6 代码),那么您可以使用Set内置于 ES6 的对象。它具有非常好的功能,可以在您的环境中按原样使用。
Set
对于 ES5 环境中的许多简单的事情,使用 Object 效果很好。如果obj是您的对象并且A是一个具有您想要在集合中操作的值的变量,那么您可以执行以下操作:
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属性)之间的潜在冲突。
constructor
ES6 侧边栏: ECMAScript 6 的当前工作版本或称为 ES 2015 的东西有一个 内置的 Set 对象 。它现在在一些浏览器中实现。由于浏览器可用性随时间而变化,您可以查看此 ES6 兼容性表Set中的行以查看浏览器可用性的当前状态。
内置 Set 对象的一个优点是它不会像 Object 那样将所有键强制转换为字符串,因此您可以将 5 和 “5” 作为单独的键。而且,您甚至可以直接在集合中使用对象,而无需进行字符串转换。这是一篇文章,描述了 Set 对象的一些功能和MDN 的文档。
我现在已经为 ES6 set 对象编写了一个 polyfill,因此您现在可以开始使用它,如果浏览器支持它,它将自动遵循内置的 set 对象。这样做的好处是您编写的 ES6 兼容代码可以一直工作到 IE7。但是,有一些缺点。ES6 集合接口利用了 ES6 迭代器,因此您可以执行类似的操作for (item of mySet),它会自动为您遍历集合。但是,这种语言特性无法通过 polyfill 实现。您仍然可以在不使用新的 ES6 语言特性的情况下迭代 ES6 集合,但坦率地说,如果没有新的语言特性,它不如我在下面包含的其他集合界面那么方便。
for (item of mySet)
在查看两者之后,您可以决定哪一个最适合您。ES6 set polyfill 在这里:https ://github.com/jfriend00/ES6-Set 。
仅供参考,在我自己的测试中,我注意到 Firefox v29 Set 实现在当前的规范草案中并不完全是最新的。例如,您不能.add()像规范描述和我的 polyfill 支持那样链接方法调用。这可能是一个动态规范的问题,因为它尚未最终确定。
.add()
Pre-Built Set 对象: 如果您想要一个已构建的对象,该对象具有可在任何浏览器中使用的集合上的操作方法,您可以使用一系列不同的预构建对象来实现不同类型的集合。有一个 miniSet,它是实现集合对象基础的小代码。它还有一个功能更丰富的 set 对象和几个派生类,包括 Dictionary(让您存储/检索每个键的值)和 ObjectSet(让您保留一组对象 - JS 对象或 DOM 对象,您可以在其中提供为每个生成唯一密钥的函数,否则 ObjectSet 将为您生成密钥)。
这是 miniSet 的代码副本(最新代码在 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;