2016-01-30 7 views
7

स्विफ्ट भाषा में एक शानदार enum समर्थन है। न केवल मामलों के साथ एक मानक enum परिभाषित कर सकते हैं, लेकिन मामलों में वैकल्पिक मूल्य "उनके साथ जुड़े" हो सकते हैं।जावास्क्रिप्ट में संबंधित मूल्यों के साथ स्विफ्ट-जैसी enums कैसे कार्यान्वित करें?

उदाहरण के लिए

, स्विफ्ट डॉक्स से लिया: एक एक मूल्य में गुजर, इसलिए तरह से एक बारकोड enum बना सकते हैं

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

इस तरह की है कि:

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

और भी switch पर productBarcode संबंधित मूल्य को पुनः प्राप्त करने के बाद की तारीख में (int एस का एक टुपल)।


मैं (, ES5 विशेष रूप से) जावास्क्रिप्ट में enum इस प्रकार की प्रणाली को लागू करने की कोशिश कर दिया गया है, लेकिन एक दीवार से टकराने कर रहा हूँ। एक enum प्रणाली, विशेष रूप से संबंधित मूल्यों के साथ एक संरचना का सबसे अच्छा तरीका क्या है?

+1

यार, मैं मुसीबत ही बात कर रहे हो रही किया गया है। मैं उन्हें स्विफ्ट में प्यार करता हूं, लेकिन उन्हें जावास्क्रिप्ट में चाहिए! – boztalay

उत्तर

3

यह बिल्कुल सही तरीका है जो मुझे पता है कि अधिकांश भाषाओं में enums काम करते हैं। आम तौर पर वे इन राज्यों में से एक के रूप में मूल्य टाइप करने के तरीके की तरह हैं। संभावित मूल्यों के एक सेट से एक मान का चयन करने की तरह। और सादे पूर्णांक के विपरीत, ऐसा करने में टाइप-सुरक्षा सुनिश्चित करने के लिए।

आपने अपने कोड में क्या पोस्ट किया है, मैं कारखाने के तरीकों के साथ एक सादा वस्तु कहूंगा।

चूंकि उन्हें उस भाषा द्वारा समर्थित नहीं किया गया है, जिस तरह से आपको उन्हें लागू करना है ताकि आपकी आवश्यकताओं को यथासंभव उपयुक्त हो सके। तो आप किस व्यवहार की अपेक्षा करते हैं।

इस बीच में त्वरित विवरणों पर दिए गए विवरणों के आधार पर एक कार्यान्वयन। उम्मीद है कि यह आपके अपेक्षा के करीब आता है:

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; 
} 

सावधान रहें, इस कोड को कुछ और संशोधन की आवश्यकता हो सकती है। उदाहरण:

कारखानों

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); 
}); 

वस्तुओं और प्रिमिटिव

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 

पहचान

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 
संबंधित मुद्दे