超级好用的JavaScript集合类Set

作者:admin     字体:[增加 减小]    类型:原创
JavaScript语言并没有集合这种数据类型,但是JavaScript这门超级灵活的语言在写一个集合类时,也不费吹灰之力。

集合是一种数据类型,用以表示非重复值的无序集合。

集合(Set)的基础方法包括添加值检测值是否在集合中,这种集合需要一种通用的实现,以保证操作效率。JavaScript的对象是属性名以及与之对应的值的基本集合。因此将对象只用做字符串的集合是大材小用。下面的代码实现了一个更加通用的Set类,它实现了从JavaScript值到唯一字符串的映射,然后将字符串用到属性名。对象和函数都不具备如此简明可靠的唯一字符串表示。因此集合类必须给集合的每一个对象或函数定义一个唯一的属性标识。

//集合类
//集合(set)是一种数据结构,用以表示非重复值的无序集合
function Set( ){
    this.values = {};
    this.n = 0;
    this.add.apply(this,arguments);
}

//将每个参数都添加至集合中
Set.prototype.add = function() {
    for ( var i=0;i<arguments.length;i++ ) {    //遍历每个参数
        var val = arguments[i];     //待添加到集合的值
        var str = Set._v2s(val);    //把它转换为字符串
        if ( !this.values.hasOwnProperty(str) ) {   //如果不在集合中
            this.values[str] = val; //将字符串和值对应起来
            this.n++;   //集合中值的计数加一
        }
    }
    return this;    //支持链式方法调用
}

//从集合删除元素,这些元素由参数指定
Set.prototype.remove = function() {
    var self = this;
    Array.prototype.map.call(arguments,function(e){ //遍历每个参数
        var str = Set._v2s(e);  //将字符串和值对应起来    
        if( self.values.hasOwnProperty(str) ) { //如果它在集合中
            delete self.values[str];        //删除它
            this.n--;   //集合中值的计数减一
        }
    });
    return this;    //支持链式方法调用
}


//如果集合包含这个值,则返回true,否则,返回false
Set.prototype.contains = function(value) {
    return this.values.hasOwnProperty(Set._v2s(value));
}

//返回集合的大小
Set.prototype.size = function() {
    return this.n;
}

//遍历集合中的所有幸免于难,在指定的上下文中调用f
Set.prototype.foreach = function(f,context) {
    for( var s in this.values ) {   //遍历集合中的所有键值
        if (this.values.hasOwnProperty(s))  //忽略继承的属性
            f.call(context,this.values[s]); //调用f,传入value
    }
}

//这是一个内部函数,用以将任意JavaScript值和唯一的字符串对应起来
Set._v2s = function( val ) {
    var self = this;
    switch( val ) {
        case undefined : return 'u';    //特殊的原始值
        case null:  return 'n';         //值只有一个字母
        case true:  return 't';
        case false: return 'f';
        default: switch( typeof val ) {
            case 'number': return '#' + val;    //数字都带有#前缀
            case 'string': return '"' + val;    //字符串都带有"前缀
            default: return '@' + objectId(val);    //Objs and funcs get @
        }   
    }

    function objectId(o) {
        var prop = "|**objectid**|";    //私有属性,用以存放id
        if (!o.hasOwnProperty(prop) ) { //如果对象没有id
            o[prop] = self._v2s.next++; //将下一个值赋给它
        }
        return o[prop];     //返回这个id
    }
}

Set._v2s.next = 1000;   //设置初始id的值


//下面是测试代码
var str = '泡泡脚本网',
    number = 999,
    n = null,
    u = undefined,
    swim = function(){ console.log('swim') },
    obj = {x:1,y:1,r:2};

var set = new Set(str,number,n,u,swim,obj);

set.remove(999)
//console.log(set);

在这里需要注意一点,字符串的键值随字符串内容长度,如果字符串长度过长,或者集合中有大量的字符串,那么上面的_v2s()处理字符串值时就显得效率低下了,为了弥补其不足,可以将字符串的键值采用md5()函数加密的结果。关于md5函数,这是一个自定义函数,JavaScript语言本身并没有提供,但是大家可以参考本站的原创文章《JavaScript写的md5加密函数,32位,结果跟php完全一样》。所以最终修改后的代码是

//集合类
//集合(set)是一种数据结构,用以表示非重复值的无序集合
function Set( ){
    this.values = {};
    this.n = 0;
    this.add.apply(this,arguments);
}

//将每个参数都添加至集合中
Set.prototype.add = function() {
    for ( var i=0;i<arguments.length;i++ ) {    //遍历每个参数
        var val = arguments[i];     //待添加到集合的值
        var str = Set._v2s(val);    //把它转换为字符串
        if ( !this.values.hasOwnProperty(str) ) {   //如果不在集合中
            this.values[str] = val; //将字符串和值对应起来
            this.n++;   //集合中值的计数加一
        }
    }
    return this;    //支持链式方法调用
}

//从集合删除元素,这些元素由参数指定
Set.prototype.remove = function() {
    var self = this;
    Array.prototype.map.call(arguments,function(e){ //遍历每个参数
        var str = Set._v2s(e);  //将字符串和值对应起来    
        if( self.values.hasOwnProperty(str) ) { //如果它在集合中
            delete self.values[str];        //删除它
            this.n--;   //集合中值的计数减一
        }
    });
    return this;    //支持链式方法调用
}


//如果集合包含这个值,则返回true,否则,返回false
Set.prototype.contains = function(value) {
    return this.values.hasOwnProperty(Set._v2s(value));
}

//返回集合的大小
Set.prototype.size = function() {
    return this.n;
}

//遍历集合中的所有幸免于难,在指定的上下文中调用f
Set.prototype.foreach = function(f,context) {
    for( var s in this.values ) {   //遍历集合中的所有键值
        if (this.values.hasOwnProperty(s))  //忽略继承的属性
            f.call(context,this.values[s]); //调用f,传入value
    }
}

//这是一个内部函数,用以将任意JavaScript值和唯一的字符串对应起来
Set._v2s = function( val ) {
    var self = this;
    switch( val ) {
        case undefined : return 'u';    //特殊的原始值
        case null:  return 'n';         //值只有一个字母
        case true:  return 't';
        case false: return 'f';
        default: switch( typeof val ) {
            case 'number': return '#' + val;    //数字都带有#前缀
            case 'string': return '"' + MD5(val);   //字符串都带有"前缀,而且采用md5加密形式,以节约长度
            default: return '@' + objectId(val);    //Objs and funcs get @
        }   
    }

    function objectId(o) {
        var prop = "|**objectid**|";    //私有属性,用以存放id
        if (!o.hasOwnProperty(prop) ) { //如果对象没有id
            o[prop] = self._v2s.next++; //将下一个值赋给它
        }
        return o[prop];     //返回这个id
    }
}

Set._v2s.next = 1000;   //设置初始id的值