如何使用JavaScript中的相关值实现Swift类枚举? [英] How to implement Swift-like enums with associated values in JavaScript?

查看:103
本文介绍了如何使用JavaScript中的相关值实现Swift类枚举?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Swift语言有一个奇妙的枚举支持。不仅可以使用案例定义标准枚举,而且可以使用可选值与之相关联。



例如,从Swift文档中获取:

 枚举条码{
case UPCA(Int,Int,Int,Int)
case QRCode(String)
case其他
}

这样可以通过传入一个条形码枚举值,如下所示:



var productBarcode = Barcode.UPCA(8,85909,51226,3) p $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ code code code code相关联的值(元组 int s)。






我一直试图在JavaScript(特别是ES5)中实现这种枚举系统,但正在墙上。枚举系统的最佳方式是什么,特别是枚举系统的最佳方法?

解决方案

我知道的大多数语言通常,它们更像是将值作为其中一个状态键入的方式。喜欢从一组可能的值中选择一个值。为了确保类型安全性,与普通的整数不同。



你在代码中发布了什么,我将使用工厂方法调用一个简单的对象。 p>

由于这些语言不支持,所以您必须以尽可能好的方式实现您的需求。所以总结一下你期望什么样的行为。



同时,基于我在swift枚举中发现的描述的实现。希望它接近你的期望:

  var odp = {
可写:4,

//两个帮助器与Object.defineProperty。
value:function(obj,prop,v,flags){
this.configurable = Boolean(flags& odp.CONFIGURABLE);
this.writable = Boolean(flags& odp.WRITABLE);
this.enumerable = Boolean(flags& odp.ENUMERABLE);
this.value = v;
Object.defineProperty(obj,prop,this);
this.value = null; // v可能是一个函数或一个对象:删除引用
return obj;
} .bind({//缓存基本定义
值:null,
可配置:false,
可写:false,
枚举:false
}),

访问器:function(obj,prop,getter,setter){
this.get = getter ||不确定的;
this.set = setter ||不确定的;
Object.defineProperty(obj,prop,this);
this.get = null;
this.set = null;
return obj;
} .bind({get:null,set:null})
}
//使这些值不可变
odp.value(odp,CONFIGURABLE,1,odp .ENUMERABLE);
odp.value(odp,WRITABLE,2,odp.ENUMERABLE);
odp.value(odp,ENUMERABLE,4,odp.ENUMERABLE);



//政策:
// 1。我不会f ***关心在定义上的键是自己的或继承的键。
//因为你把它们传给我,我想你要我来处理它们。

// 2。如果我发现一些未定义的值,我忽略它,好像不在那里。
//使用null来表示一些空值

// name和extendProto是可选的
函数Enum(name,description,extendProto){
var n = name,d = description,xp = extendProto;
if(n&&" typeof n ===object)xp = d,d = n,n = null;
var xpf = typeof xp ===function&& XP;
var xpo = typeof xp ===object&& XP;

函数类型(){
throw new错误(枚举不应该手动创建);
}

//滥用filter()as forEach()
//删除同一步骤中未定义的密钥。
var keys = Object.keys(d).filter(function(key){
var val = d [key];
if(val === undefined)return false;
var proto = Object.create(type.prototype);

//您有机会使用其他属性扩展特定的原型
//像添加其他类型的原型方法
var props = xpf || xpo&& xpo [key];
if(typeof props ===function)
props = props.call(type,proto,key ,val);

if(props&& typeof props ===object&& props!== proto&& props!== val){
var flags = odp.CONFIGURABLE + odp.WRITABLE;
for(var k in props)props [k] === undefined || odp.value(proto,k,props [k],flags);
if(lengthin props)odp.value(props,length,props.length,flags);
}

if(typeof val ===function ){
//工厂和typedefinition在th同样的类型
//调用这个函数来创建一个这个枚举类型的新对象
//和同时这个函数的类型
type [key] = function (){
var me = Object.create(proto);
var props = val.apply(me,arguments);
如果(props&& typeof props ===object&& props!== me){
for(var k in props)props [k] === undefined | | odp.value(me,k,props [k],odp.ENUMERABLE);
if(lengthin props)odp.value(me,length,props.length);
}
返回我;
}
//修复此工厂的fn.length属性
odp.value(type [key],length,val.length,odp.CONFIGURABLE);

//更改此工厂的名称
odp.value(type [key],name,(n ||enum)+{+ key +} || key,odp.CONFIGURABLE);

type [key] .prototype = proto;
odp.value(proto,constructor,type [key],odp.CONFIGURABLE);

} else if(val&& typeof val ===object){
for(var k in val)val [k] === undefined || odp.value(proto,k,val [k]);
if(lengthin val)odp.value(proto,length,val.length);
type [key] = proto;

} else {
//这个enum的类型的对象包装原始
//有点像String或Number或Boolean类

//所以记住,当处理这种值时,
//你不处理实际的基元
odp.value(proto,valueOf,function(){return val ;});
type [key] = proto;

}

return true;
});

odp.value(type,name,n ||enum [+ keys.join(,)+],odp.CONFIGURABLE);
Object.freeze(type);

返回类型;
}

请注意,此代码可能需要进一步修改。示例:



工厂

 函数uint(v){return v>>> 0} 

var条形码=枚举(条形码,{
QRCode:function(string){
// this指的是两种类型的对象,条形码和条形码.QRCode
//可以根据需要修改它
odp.value(this,valueOf,function(){return string},true) ;
},

UPCA:function(a,b,c,d){
//您还可以返回一个对象,其中要添加的属性
//和数组,...
return [
uint(a),
uint(b),
uint(c),
uint(d)
];
//但要小心,这不会添加Array.prototype-methods !!!

//事件这将工作,并被处理像一个数组
return arguments;
},

其他:function(properties){
return properties; // some sugar
}
});

var productBarcode = Barcode.UPCA(8,85909,51226,3);
console.log(productBarcode is Barcode:,productBarcode instance of Barcode); // true
console.log(productBarcode is Barcode.UPCA:,productBarcode instanceof Barcode.UPCA); // true

console.log(productBarcode is Barcode.Other:,productBarcode instanceof Barcode.Other); // false

console.log(access values:,productBarcode [0],productBarcode [1],productBarcode [2],productBarcode [3],productBarcode.length);

Array.prototype.forEach.call(productBarcode,function(value,index){
console.log(index:,index,value:,value);
});

对象和基元

  var indexes = Enum({
lo:{from:0,to:13},
hi:{from:14,to:42} ,

avg:7
});

var lo = indices.lo;
console.log(lo是一个有效的索引,lo instanceof索引);
console.log(lo is indexes.lo,lo === indices.lo);
//indices.lo总是引用相同的对象
//没有函数调用,没有getter!

var avg = indices.avg; //请注意,这不是原始的,它被包裹

console.log(avg是一个有效的索引,avg instanceof索引);
console.log(与基元比较:);
console.log( - typesafe,avg === 7); // false,因为avg被包裹了!
console.log( - loose,avg == 7); // true
console.log( - typecast + typesafe,Number(avg)=== 7); // true

//可能的用法就像是原始的。
for(var i = lo.from; i< lo.to; ++ i){
console.log(i,i == avg); //看看第一个输出;)
}

//但是如果你想使用一些原型方法
//(像正确的toString()对数字的方法,或字符串上的substr)
//确保你有一个正确的原语!

var out = avg.toFixed(3);
//将失败,因为此对象不提供Number

// + avg的原型方法与Number(avg)相同
var out =(+ AVG).toFixed(3); //将成功

身份

  var def = {foo:42}; 

var obj =枚举({
a:13,
b:13,
c:13,

obj1:def,
obj2:def
});

//虽然所有三个都有/表示相同的值,但它们不一样
var v = obj.a;
console.log(testing a,v === obj.a,v === obj.b,v === obj.c); // true,false,false

var v = obj.b;
console.log(testing a,v === obj.a,v === obj.b,v === obj.c); // false,true,false

var v = obj.c;
console.log(testing a,v === obj.a,v === obj.b,v === obj.c); // false,false,true


console.log(比较对象,obj.obj1 === obj.obj2); // false
console.log(compare property foo,obj.obj1.foo === obj.obj2.foo); // true

//与工厂函数提供的值相同:
console.log(将两个调用与相同的args比较);
console.log(Barcode.Other()=== Barcode.Other(),Barcode.Other()=== Barcode.Other());
//将失败,因为工厂不缓存,
//每个调用创建一个新的Object实例。
//如果你需要检查他们是否相等,写一个这样做的功能。

extendProto

  //你有机会在枚举
//中扩展每个次级条目的原型//也许你想从一些其他原型中添加一些方法
// like String.prototype或者iterator-methods,或者一个用于等式检查的方法...

var Barcode =枚举(条形码,{/ *工厂* /},函数(proto,值){
var _barcode = this;
//所以你可以访问闭包中的枚举,而不需要一个全局变量
//但是如果你把这个,你是调试你生成的错误的一个

//这个函数在这个枚举条目的prototpe-object被创建后执行
//,然后再进行任何修改
//既不是这个特定的条目,也没有枚举本身也没有完成,所以不要混淆它们

//这个方法的唯一目的是为你提供一个钩
//添加更多p对原型对象的roperties

// aou也可以返回一个具有添加到原型对象的属性的对象。
//这些属性将被添加为可配置和可写,但不能枚举。
//而不是getter或setter。如果您需要更多的控制,请随意修改您自己的原型。
return {
isBarcode:function(){
return this instanceof _barcode;
}
}
});

//或者您可以为每个单个属性定义它,
//所以您不必在一个巨大的函数中打开不同的属性
var Barcode =枚举(条形码,{/ *工厂* /},{
UPCA:function(proto,key,value){
//与通用函数
//相同的行为将只执行UPCA的原型

var _barcode = this; // aka条形码在这种情况下
var AP = [];
return {
/ / copy map和indexOf从Array原型
map:AP.map,
indexOf:AP.indexOf,

//并添加一个自定义toString和克隆方法到原型
toString:function(){
returnUPCA [+ AP.join.call(this,,)+];
},
clone: function(){
return _barcode.UPCA.apply(null,this);
}
};
},

// OR
QRCode:{
//或简单地定义一个包含属性/方法的对象
//应该添加到QRCode的原型
//再次可配置和可写但不能枚举

substr:String.prototype.substr,
substring:String.prototype.substring,
charAt:String.prototype.charAt,
charCodeAt:String .prototype.charCodeAt
}
});
// mixin函数和对象可以混合


The Swift language has a fantastic enum support. Not only can one define a standard enum with cases, but cases can have optional values "associated to them."

For example, taken from the Swift docs:

enum Barcode {
    case UPCA(Int, Int, Int, Int)
    case QRCode(String)
    case Other
}

Such that one could create a Barcode enum by passing in a value, like so:

var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)

and also switch on productBarcode at a later date to retrieve the associated value (a tuple of ints).


I have been trying to implement this kind of enum system in JavaScript (ES5, in particular), but am hitting a wall. What is the best way to structure an enum system, especially one with associated values?

解决方案

That's notr exactly the way enums work in most languages i know. Usually they are more like a way to type a value as one of these states. Like selecting one value out of a set of possible values. And to ensure type-safety in doing this, unlike with plain integers.

What you posted in your code, I would call a plain Object with factory-methods.

Since they are not supported that way by the language you have to implement them in a way that fit's your needs as good as possible. So sum up what behaviour you expect.

In the mean time a Implementation based on the descriptions i've found on swift enums. hope it comes close to what you expect:

var odp = {
    ENUMERABLE: 4,

    //two helper with Object.defineProperty.
    value: function(obj, prop, v, flags){
        this.configurable = Boolean(flags & odp.CONFIGURABLE);
        this.writable = Boolean(flags & odp.WRITABLE);
        this.enumerable = Boolean(flags & odp.ENUMERABLE);
        this.value = v;
        Object.defineProperty(obj, prop, this);
        this.value = null;  //v may be a function or an object: remove the reference
        return obj;
    }.bind({    //caching the basic definition
        value: null, 
        configurable: false, 
        writable: false, 
        enumerable: false 
    }),

    accessor: function(obj, prop, getter, setter){
        this.get = getter || undefined;
        this.set = setter || undefined;
        Object.defineProperty(obj, prop, this);
        this.get = null;
        this.set = null;
        return obj;
    }.bind({ get: null, set: null })
}
//make these values immutable
odp.value(odp, "CONFIGURABLE", 1, odp.ENUMERABLE);
odp.value(odp, "WRITABLE", 2, odp.ENUMERABLE);
odp.value(odp, "ENUMERABLE", 4, odp.ENUMERABLE);



//Policy: 
//1. I don't f*** care wether the keys on the definition are own or inherited keys.
//since you pass them to me, I suppose you want me to process them.

//2. If i find some undefined-value i ignore it, as if it wasn't there.
//use null to represent some "empty" value

//name and extendProto are optional
function Enum(name, description, extendProto){
    var n = name, d = description, xp=extendProto;
    if(n && typeof n === "object") xp=d, d = n, n = null;
    var xpf = typeof xp === "function" && xp;
    var xpo = typeof xp === "object" && xp;

    function type(){ 
        throw new Error("enums are not supposed to be created manually"); 
    }

    //abusing filter() as forEach()
    //removing the keys that are undefined in the same step.
    var keys = Object.keys(d).filter(function(key){
        var val = d[key];
        if(val === undefined) return false;
        var proto = Object.create(type.prototype);

        //your chance to extend the particular prototype with further properties
        //like adding the prototype-methods of some other type
        var props = xpf || xpo && xpo[key];
        if(typeof props === "function") 
            props = props.call(type, proto, key, val);

        if(props && typeof props === "object" && props !== proto && props !== val){
            var flags = odp.CONFIGURABLE+odp.WRITABLE;
            for(var k in props) props[k]===undefined || odp.value(proto, k, props[k], flags);
            if("length" in props) odp.value(props, "length", props.length, flags);
        }

        if(typeof val === "function"){
            //a factory and typedefinition at the same type
            //call this function to create a new object of the type of this enum
            //and of the type of this function at the same time
            type[key] = function(){
                var me = Object.create(proto);
                var props = val.apply(me, arguments);
                if(props && typeof props === "object" && props !== me){
                    for(var k in props) props[k]===undefined || odp.value(me, k, props[k], odp.ENUMERABLE);
                    if("length" in props) odp.value(me, "length", props.length);
                }
                return me;
            }
            //fix the fn.length-property for this factory
            odp.value(type[key], "length", val.length, odp.CONFIGURABLE);

            //change the name of this factory
            odp.value(type[key], "name", (n||"enum")+"{ "+key+" }" || key, odp.CONFIGURABLE);

            type[key].prototype = proto;
            odp.value(proto, "constructor", type[key], odp.CONFIGURABLE);

        }else if(val && typeof val === "object"){
            for(var k in val) val[k] === undefined || odp.value(proto, k, val[k]);
            if("length" in val) odp.value(proto, "length", val.length);
            type[key] = proto;

        }else{
            //an object of the type of this enum that wraps the primitive
            //a bit like the String or Number or Boolean Classes

            //so remember, when dealing with this kind of values, 
            //you don't deal with actual primitives
            odp.value(proto, "valueOf", function(){ return val; });     
            type[key] = proto;

        }

        return true;
    });

    odp.value(type, "name", n || "enum[ " + keys.join(", ") + " ]", odp.CONFIGURABLE);
    Object.freeze(type);

    return type;
}

Beware, this code may need some further modification. Examples:

Factories

function uint(v){ return v>>>0 }

var Barcode = Enum("Barcode", {
    QRCode: function(string){
        //this refers to an object of both types, Barcode and Barcode.QRCode
        //aou can modify it as you wish
        odp.value(this, "valueOf", function(){ return string }, true);
    },

    UPCA: function(a,b,c,d){
        //you can also return an object with the properties you want to add
        //and Arrays, ...
        return [
            uint(a), 
            uint(b), 
            uint(c), 
            uint(d)
        ];
        //but beware, this doesn't add the Array.prototype-methods!!!

        //event this would work, and be processed like an Array
        return arguments;
    },

    Other: function(properties){ 
        return properties;  //some sugar
    }
});

var productBarcode = Barcode.UPCA(8, 85909, 51226, 3);
console.log("productBarcode is Barcode:", productBarcode instanceof Barcode);   //true
console.log("productBarcode is Barcode.UPCA:", productBarcode instanceof Barcode.UPCA); //true

console.log("productBarcode is Barcode.Other:", productBarcode instanceof Barcode.Other);   //false

console.log("accessing values: ", productBarcode[0], productBarcode[1], productBarcode[2], productBarcode[3], productBarcode.length);

Array.prototype.forEach.call(productBarcode, function(value, index){
    console.log("index:", index, "  value:", value);
});

Objects and Primitives

var indices = Enum({
    lo: { from: 0, to: 13 },
    hi: { from: 14, to: 42 },

    avg: 7
});

var lo = indices.lo;
console.log("lo is a valid index", lo instanceof indices);
console.log("lo is indices.lo", lo === indices.lo); 
//indices.lo always references the same Object
//no function-call, no getter!

var avg = indices.avg;  //beware, this is no primitive, it is wrapped

console.log("avg is a valid index", avg instanceof indices);
console.log("comparison against primitives:");
console.log(" - typesafe", avg === 7);  //false, since avg is wrapped!!!
console.log(" - loose", avg == 7);  //true
console.log(" - typecast+typesafe", Number(avg) === 7); //true

//possible usage like it was a primitive.
for(var i=lo.from; i<lo.to; ++i){
    console.log(i, i == avg);   //take a look at the first output ;)
}

//but if you want to use some of the prototype methods 
//(like the correct toString()-method on Numbers, or substr on Strings)
//make sure that you have a proper primitive!

var out = avg.toFixed(3);
//will fail since this object doesn't provide the prototype-methods of Number

//+avg does the same as Number(avg)
var out = (+avg).toFixed(3);    //will succeed

Identity

var def = { foo: 42 };

var obj = Enum({
    a: 13,
    b: 13,
    c: 13,

    obj1: def,
    obj2: def
});

//although all three have/represent the same value, they ain't the same
var v = obj.a;
console.log("testing a", v === obj.a, v === obj.b, v===obj.c);  //true, false, false

var v = obj.b;
console.log("testing a", v === obj.a, v === obj.b, v===obj.c);  //false, true, false

var v = obj.c;
console.log("testing a", v === obj.a, v === obj.b, v===obj.c);  //false, false, true


console.log("comparing objects", obj.obj1 === obj.obj2);    //false
console.log("comparing property foo", obj.obj1.foo === obj.obj2.foo);   //true

//same for the values provided by the factory-functions:
console.log("compare two calls with the same args:");
console.log("Barcode.Other() === Barcode.Other()", Barcode.Other() === Barcode.Other());
//will fail, since the factory doesn't cache, 
//every call creates a new Object instance.
//if you need to check wether they are equal, write a function that does that.

extendProto

//your chance to extend the prototype of each subordinated entry in the enum
//maybe you want to add some method from some other prototype 
//like String.prototype or iterator-methods, or a method for equality-checking, ...

var Barcode = Enum("Barcode", {/* factories */}, function(proto, key, value){
    var _barcode = this;    
    //so you can access the enum in closures, without the need for a "global" variable.
    //but if you mess around with this, you are the one to debug the Errors you produce.

    //this function is executed right after the prototpe-object for this enum-entry is created
    //and before any further modification.
    //neither this particular entry, nor the enum itself are done yet, so don't mess around with them.

    //the only purpose of this method is to provide you a hook 
    //to add further properties to the proto-object

    //aou can also return an object with properties to add to the proto-object.
    //these properties will be added as configurable and writable but not enumerable.
    //and no getter or setter. If you need more control, feel free to modify proto on you own.
    return {
        isBarcode: function(){
            return this instanceof _barcode;
        }
    }
});

//OR you can define it for every single property, 
//so you don't have to switch on the different properties in one huge function
var Barcode = Enum("Barcode", {/* factories */}, {
    "UPCA": function(proto, key, value){
        //same behaviour as the universal function
        //but will be executed only for the proto of UPCA

        var _barcode = this;    //aka Barcode in this case
        var AP = [];
        return { 
            //copy map and indexOf from the Array prototype
            map: AP.map,
            indexOf: AP.indexOf, 

            //and add a custom toString and clone-method to the prototype
            toString: function(){
                return "UPCA[ "+AP.join.call(this, ", ")+" ]";
            },
            clone: function(){
                return _barcode.UPCA.apply(null, this);
            } 
        };
    },

    //OR
    "QRCode": {
        //or simply define an object that contains the properties/methods 
        //that should be added to the proto of QRCode
        //again configurable and writable but not enumerable

        substr: String.prototype.substr,
        substring: String.prototype.substring,
        charAt: String.prototype.charAt,
        charCodeAt: String.prototype.charCodeAt
    }
});
//mixin-functions and objects can be mixed

这篇关于如何使用JavaScript中的相关值实现Swift类枚举?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆