用JavaScript模仿套装?

Ric*_*ard 218 javascript

我在使用JavaScript.我想存储一个唯一的,无序的字符串值列表,其中包含以下属性:

  1. 一个快速的方式来问'列表中的A'?
  2. 一种快速的方法'从列表中删除A(如果列表中存在)
  3. 一种快速的方法'如果它尚未存在,则将'添加到列表'.

我真正想要的是一套.有关在JavaScript中模拟集合的最佳方法的任何建议吗?

这个问题建议使用一个Object,键存储属性,并且值都设置为true:这是一种明智的方法吗?

jfr*_*d00 261

如果您在支持ES6的环境中编程(例如node.js,具有您需要的ES6功能的特定浏览器或为您的环境转换ES6代码),那么您可以使用SetES6中内置对象.它具有非常好的功能,可以在您的环境中使用.


对于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};
Run Code Online (Sandbox Code Playgroud)

问题1:A在列表中:

if (A in obj) {
    // put code here
}
Run Code Online (Sandbox Code Playgroud)

问题2:如果有,请从列表中删除"A":

delete obj[A];
Run Code Online (Sandbox Code Playgroud)

问题3:如果列表中没有"A",则将其添加到列表中

obj[A] = true;
Run Code Online (Sandbox Code Playgroud)

为了完整性,对A列表中是否的测试更安全:

if (Object.prototype.hasOwnProperty.call(obj, A))
    // put code here
}
Run Code Online (Sandbox Code Playgroud)

因为内置方法和/或基础对象上的属性之间可能存在冲突,如constructor属性.


ES6上的补充工具栏:ECMAScript 6的当前工作版本或称为ES 2015的东西有一个内置的Set对象.它现在在一些浏览器中实现.由于浏览器的可用性随时间的变化,你可以看一下线Set此ES6兼容性表,查看浏览器可用性的当前状态.

内置Set对象的一个​​优点是它不会像对象一样强制字符串的所有键,因此您可以同时使用5和"5"作为单独的键.并且,您甚至可以直接在集合中使用对象而无需字符串转换.下面是一篇文章,描述了一些功能和MDN的文档设置对象.

我现在已经为ES6 set对象编写了一个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代码的副本(最新的代码在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;
Run Code Online (Sandbox Code Playgroud)

  • 这解决了这个问题,但要明确的是,这个实现_won't_不适用于整数或字符串之外的东西. (15认同)
  • 要获取列表中的项,可以使用`Object.keys(obj)`. (4认同)
  • @mkirk - 是的,您在集合中索引的项目必须具有可以作为索引键的字符串表示形式(例如,它是字符串或具有唯一描述该项目的toString()方法). (3认同)
  • @Blixt - `Object.keys()`需要IE9,FF4,Safari 5,Opera 12或更高版本.有一个旧版浏览器的polyfill [这里](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys). (3认同)

Tho*_*isé 72

您可以创建一个没有类似属性的Object

var set = Object.create(null)
Run Code Online (Sandbox Code Playgroud)

它可以充当一组并消除使用的需要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
Run Code Online (Sandbox Code Playgroud)

  • 如果你只使用`set = {}`它将继承Object的所有属性(例如`toString`),所以你必须在`if(if)中使用`hasOwnProperty`来检查集合的有效负载(你添加的属性). A in set)` (13认同)
  • 我不知道可以创建一个完全空的对象.谢谢,您的解决方案非常优雅. (6认同)

hym*_*oth 23

从ECMAScript 6开始,Set数据结构是一个内置功能.可以在此处找到与node.js版本的兼容性.

  • 你好,为了清楚起见 - 现在是2014年,这仍然是Chrome的实验性的吗?如果不是,你能编辑你的答案吗?谢谢 (4认同)

Sal*_*ali 14

在ES6版本的Javascript中,您已经内置了set类型(检查与浏览器的兼容性).

var numbers = new Set([1, 2, 4]); // Set {1, 2, 4}
Run Code Online (Sandbox Code Playgroud)

要将一个元素添加到您只需使用的集合中,该集合.add()将运行O(1)并添加要设置的元素(如果它不存在)或者如果它已经存在则不执行任何操作.你可以在那里添加任何类型的元素(数组,字符串,数字)

numbers.add(4); // Set {1, 2, 4}
numbers.add(6); // Set {1, 2, 4, 6}
Run Code Online (Sandbox Code Playgroud)

检查集合中的元素数量,您只需使用即可.size.也参加O(1)

numbers.size; // 4
Run Code Online (Sandbox Code Playgroud)

为了从该组删除元素使用.delete().如果值存在(并且已被删除),则返回true;如果值不存在,则返回false.也参加O(1).

numbers.delete(2); // true
numbers.delete(2); // false
Run Code Online (Sandbox Code Playgroud)

检查元素是否存在一组使用.has(),如果元素是集合,否则为false返回true.也参加O(1).

numbers.has(3); // false
numbers.has(1); // true
Run Code Online (Sandbox Code Playgroud)

除了你想要的方法之外,还有一些方法:

  • numbers.clear(); 只会删除集合中的所有元素
  • numbers.forEach(callback); 以插入顺序迭代集合的值
  • numbers.entries(); 创建所有值的迭代器
  • numbers.keys(); 返回与之相同的集合的键 numbers.values()

还有一个Weakset,它允许只添加对象类型的值.


mcr*_*isc 10

我已经开始了一个集合的实现,目前在数字和字符串方面效果很好.我的主要关注点是差异操作,所以我试图尽可能高效.叉子和代码评论是受欢迎的!

https://github.com/mcrisc/SetJS


kon*_*ych 9

我刚刚注意到d3.js库具有集合,映射和其他数据结构的实现.我不能争论他们的效率,但从它是一个受欢迎的图书馆来判断它必须是你所需要的.

文档在这里

为方便起见,我从链接中复制(前3个功能是感兴趣的)


  • d3.set([数组])

构造一个新集.如果指定了array,则将给定的字符串值数组添加到返回的集合中.

  • set.has(值)

当且仅当此set具有指定值字符串的条目时,才返回true.

  • set.add(值)

将指定的值字符串添加到此集合.

  • set.remove(值)

如果集合包含指定的值字符串,则将其删除并返回true.否则,此方法不执行任何操作并返回false.

  • set.values()

返回此set中字符串值的数组.返回值的顺序是任意的.可以用作计算一组字符串的唯一值的便捷方式.例如:

d3.set(["foo","bar","foo","baz"]).values(); //"foo","bar","baz"

  • set.forEach(功能)

为此set中的每个值调用指定的函数,并将该值作为参数传递.函数的这个上下文就是这个集合.返回undefined.迭代顺序是任意的.

  • set.empty()

当且仅当此set具有零值时返回true.

  • set.size()

返回此集合中的值的数量.