2016-01-30 21 views
7

Ngôn ngữ Swift có hỗ trợ enum tuyệt vời. Không chỉ có thể xác định một enum chuẩn với các trường hợp, nhưng các trường hợp có thể có các giá trị tùy chọn "được liên kết với chúng".Làm thế nào để thực hiện các Swift enums với các giá trị liên quan trong JavaScript?

Ví dụ, lấy từ các tài liệu Swift:

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

như vậy mà người ta có thể tạo ra một enum mã vạch bằng cách đi qua trong một giá trị, như vậy:

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

và cũng switch trên productBarcode vào một ngày sau đó để truy lục giá trị được liên kết (một bộ gồm int s).


Tôi đã cố gắng triển khai loại hệ thống enum này trong JavaScript (đặc biệt là ES5), nhưng đang đập vào tường. Cách tốt nhất để cấu trúc một hệ thống enum, đặc biệt là một với các giá trị liên quan là gì?

+1

Man, tôi đã gặp sự cố khi làm điều tương tự. Tôi yêu chúng trong Swift, nhưng cần chúng trong JavaScript! – boztalay

Trả lời

3

Đó là notr chính xác cách enums hoạt động trong hầu hết các ngôn ngữ tôi biết. Thông thường, chúng giống như một cách để nhập một giá trị như một trong những trạng thái này. Giống như chọn một giá trị trong số các giá trị có thể có. Và để đảm bảo an toàn kiểu khi thực hiện việc này, không giống với các số nguyên đơn giản.

Những gì bạn đã đăng trong mã của mình, tôi sẽ gọi một đối tượng đơn giản với phương pháp nhà máy.

Vì chúng không được hỗ trợ theo ngôn ngữ mà bạn phải triển khai theo cách phù hợp với nhu cầu của bạn càng tốt càng tốt. Vì vậy, tổng hợp những gì bạn mong đợi hành vi.

Trong thời gian đó, việc triển khai dựa trên các mô tả tôi đã tìm thấy trên các enums nhanh. hy vọng nó đến gần với những gì bạn mong đợi:

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

Hãy coi chừng, mã này có thể cần sửa đổi thêm. Ví dụ:

nhà máy

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 và 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 

nhận dạng

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 
Các vấn đề liên quan