ค้นหาเว็บไซต์

ฉันจะรับประกันได้อย่างไรว่าคำจำกัดความของ enum ของฉันจะไม่เปลี่ยนใน JavaScript


สิ่งต่อไปนี้จะทำให้วัตถุมีคุณลักษณะทั้งหมดที่ enums มีใน JavaScript หรือไม่? บางอย่างเช่น:

my.namespace.ColorEnum = {
  RED : 0,
  GREEN : 1,
  BLUE : 2
}

// later on

if(currentColor == my.namespace.ColorEnum.RED) {
  // whatever
}

หรือมีวิธีอื่นที่ฉันสามารถทำสิ่งนี้ได้หรือไม่?

คำตอบ #1

ตั้งแต่เวอร์ชัน 1.8.5 เป็นต้นมา สามารถปิดผนึกและแช่แข็งวัตถุได้ ดังนั้นให้กำหนดข้างต้นเป็น:

const DaysEnum = Object.freeze({"monday":1, "tuesday":2, "wednesday":3, ...})

หรือ

const DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}
Object.freeze(DaysEnum)

และก็เรียบร้อย! enums ของ JS

อย่างไรก็ตาม สิ่งนี้ไม่ได้ป้องกันคุณจากการกำหนดค่าที่ไม่ต้องการให้กับตัวแปร ซึ่งมักจะเป็นเป้าหมายหลักของ enums:

let day = DaysEnum.tuesday
day = 298832342 // goes through without any errors

วิธีหนึ่งในการรับประกันความปลอดภัยของชนิดข้อมูลที่แข็งแรงมากขึ้น (ไม่ว่าจะกับเอนัมหรือไม่ก็ตาม) คือการใช้เครื่องมืออย่าง TypeScript หรือ Flow

เครื่องหมายคำพูดไม่จำเป็น แต่ฉันเก็บไว้เพื่อความสอดคล้อง

คำตอบ #2

นี่ไม่ใช่คำตอบที่มากนัก แต่ส่วนตัวฉันคิดว่ามันใช้ได้ดี

อย่างไรก็ตาม เนื่องจากไม่สำคัญว่าค่าจะเป็นเท่าไหร่ (คุณใช้ 0, 1, 2) ผมจะใช้สตริงที่มีความหมายเผื่อว่าคุณอยากจะแสดงค่าปัจจุบันออกมา

คำตอบ #3

อัปเดต

ฉันคิดว่าคำตอบของฉันด้านล่างไม่ใช่วิธีที่ดีที่สุดในการเขียน enums ใน JavaScript อีกต่อไป ดูโพสต์บล็อกของฉันสำหรับรายละเอียดเพิ่มเติม: Enums ใน JavaScript


แจ้งเตือนว่าชื่อนี้สามารถใช้ได้แล้ว:

if (currentColor == my.namespace.ColorEnum.RED) {
   // alert name of currentColor (RED: 0)
   var col = my.namespace.ColorEnum;
   for (var name in col) {
     if (col[name] == col.RED)
       alert(name);
   }
}

อีกทางเลือกหนึ่งคือคุณสามารถทำให้ค่ากลายเป็นวัตถุ เพื่อที่คุณจะได้ทั้งเค้กและกินมันด้วย:

var SIZE = {
  SMALL : {value: 0, name: "Small", code: "S"}, 
  MEDIUM: {value: 1, name: "Medium", code: "M"}, 
  LARGE : {value: 2, name: "Large", code: "L"}
};

var currentSize = SIZE.MEDIUM;
if (currentSize == SIZE.MEDIUM) {
  // this alerts: "1: Medium"
  alert(currentSize.value + ": " + currentSize.name);
}

ใน JavaScript เนื่องจากเป็นภาษาที่มีลักษณะไดนามิก จึงเป็นไปได้แม้กระทั่งที่จะเพิ่มค่าของ enum ไปยังเซตในภายหลัง:

// Add EXTRALARGE size
SIZE.EXTRALARGE = {value: 3, name: "Extra Large", code: "XL"};

โปรดจำไว้ว่า ฟิลด์ของ enum (value, name และ code ในตัวอย่างนี้) ไม่จำเป็นสำหรับการตรวจสอบเอกลักษณ์และมีไว้เพื่อความสะดวกเท่านั้น นอกจากนี้ชื่อของ property ขนาดเองก็ไม่จำเป็นต้องถูกเขียนโค้ดแบบตั้งค่าคงที่ แต่สามารถตั้งค่าได้แบบไดนามิก ดังนั้นสมมติว่าคุณทราบเพียงชื่อสำหรับค่า enum ใหม่ของคุณ คุณก็ยังสามารถเพิ่มได้โดยไม่เกิดปัญหา:

// Add 'Extra Large' size, only knowing it's name
var name = "Extra Large";
SIZE[name] = {value: -1, name: name, code: "?"};

แน่นอนว่านี่หมายความว่าบางสมมติฐานไม่สามารถทำได้อีกต่อไป (เช่น ค่าดังกล่าวแสดงลำดับที่ถูกต้องสำหรับขนาด)

จำไว้ว่า ใน JavaScript วัตถุ (object) นั้นเหมือนกับ แผนที่ หรือ ตารางแฮช ชุดของคู่ชื่อ-ค่า คุณสามารถวนลูปผ่านมันหรือต้องการจัดการมันโดยไม่ต้องรู้เกี่ยวกับมันมาก่อน

ตัวอย่าง

for (var sz in SIZE) {
  // sz will be the names of the objects in SIZE, so
  // 'SMALL', 'MEDIUM', 'LARGE', 'EXTRALARGE'
  var size = SIZE[sz]; // Get the object mapped to the name in sz
  for (var prop in size) {
    // Get all the properties of the size object, iterates over
    // 'value', 'name' and 'code'. You can inspect everything this way.        
  }
} 

และอ้อ ถ้าคุณสนใจเกี่ยวกับ namespaces คุณอาจอยากดูวิธีแก้ปัญหาของผมสำหรับการจัดการ namespace และ dependency ที่เรียบง่ายแต่ทรงพลังสำหรับ JavaScript: Packages JS

คำตอบ #4

สรุป: คุณทำไม่ได้

คุณสามารถปลอมแปลงมันได้ แต่คุณจะไม่ได้ความปลอดภัยของชนิดข้อมูล โดยทั่วไปแล้วสิ่งนี้ทำโดยการสร้างพจนานุกรมง่าย ๆ ของค่าข้อความที่แมปกับค่าจำนวนเต็ม ตัวอย่างเช่น:

var DaysEnum = {"monday":1, "tuesday":2, "wednesday":3, ...}

Document.Write("Enumerant: " + DaysEnum.tuesday);

ปัญหาของแนวทางนี้? คุณอาจจะกำหนดค่าตัวนับซ้ำโดยไม่ได้ตั้งใจ หรืออาจมีค่าตัวนับซ้ำโดยไม่ได้ตั้งใจ ตัวอย่างเช่น:

DaysEnum.monday = 4; // whoops, monday is now thursday, too

แก้ไข

แล้ว Object.freeze ของ Artur Czajka ล่ะ? มันจะไม่ช่วยป้องกันคุณจากการตั้งค่า monday เป็น thursday หรือ? - Fry Quad

แน่นอน, Object.freeze จะช่วยแก้ปัญหาที่ฉันบ่นได้ทั้งหมด ฉันอยากเตือนทุกคนว่าเมื่อฉันเขียนสิ่งข้างต้น, Object.freeze ยังไม่มีอยู่จริง

ตอนนี้.... ตอนนี้มันเปิดโอกาสที่น่าสนใจ มาก บางอย่าง

แก้ไข 2
นี่คือห้องสมุดที่ดีมากสำหรับการสร้าง enums

http://www.2ality.com/2011/10/enums.html

แม้ว่ามันอาจจะไม่เหมาะกับการใช้งาน enum ทุกกรณีที่ถูกต้อง แต่มันก็ช่วยได้มาก

คำตอบ #5

ในเบราว์เซอร์สมัยใหม่ส่วนใหญ่ มีประเภทข้อมูลพื้นฐานแบบสัญลักษณ์ซึ่งสามารถใช้สร้างการระบุค่าที่คงที่ (enumeration) ได้ มันจะช่วยรับประกันความปลอดภัยของประเภทของ enum เนื่องจากแต่ละค่าของสัญลักษณ์จะถูกการันตีโดย JavaScript ว่าไม่ซ้ำกัน เช่น Symbol() != Symbol() ตัวอย่างเช่น:

const COLOR = Object.freeze({RED: Symbol(), BLUE: Symbol()});

เพื่อให้ง่ายต่อการดีบัก คุณสามารถเพิ่มคำอธิบายให้กับค่าของ enum ได้:

const COLOR = Object.freeze({RED: Symbol("RED"), BLUE: Symbol("BLUE")});

ตัวอย่าง Plunker

บน GitHub คุณสามารถหาตัวห่อที่ช่วยทำให้โค้ดที่จำเป็นในการเริ่มต้น enum ง่ายขึ้นได้:

const color = new Enum("RED", "BLUE")

color.RED.toString() // Symbol(RED)
color.getName(color.RED) // RED
color.size // 2
color.values() // Symbol(RED), Symbol(BLUE)
color.toString() // RED,BLUE

คำตอบ #6

นี่คือสิ่งที่เราทุกคนต้องการ:

function Enum(constantsList) {
    for (var i in constantsList) {
        this[constantsList[i]] = i;
    }
}

ตอนนี้คุณสามารถสร้าง enum ของคุณได้:

var YesNo = new Enum(['NO', 'YES']);
var Color = new Enum(['RED', 'GREEN', 'BLUE']);

โดยการทำเช่นนี้ ค่าคงที่สามารถเข้าถึงได้ในวิธีปกติ (YesNo.YES, Color.GREEN) และพวกมันจะได้ค่าจำนวนเต็มเรียงลำดับ (NO=0, YES=1; RED=0, GREEN=1, BLUE=2)

คุณยังสามารถเพิ่มเมธอดได้โดยใช้ Enum.prototype:

Enum.prototype.values = function() {
    return this.allValues;
    /* for the above to work, you'd need to do
            this.allValues = constantsList at the constructor */
};

แก้ไข - ปรับปรุงเล็กน้อย - ตอนนี้มี varargs: (น่าเสียดายที่มันไม่ทำงานอย่างถูกต้องบน IE :S... ก็ควรใช้เวอร์ชันก่อนหน้านี้ต่อไป)

function Enum() {
    for (var i in arguments) {
        this[arguments[i]] = i;
    }
}

var YesNo = new Enum('NO', 'YES');
var Color = new Enum('RED', 'GREEN', 'BLUE');

คำตอบ #7

ชื่อค่าตัวแปรที่ขยายตัวได้และบรรยายตัวเอง

มาตรงไปที่ปัญหาเลย: ขนาดไฟล์ คำตอบอื่นๆ ที่ระบุไว้ที่นี่ทำให้โค้ดที่ถูกย่อของคุณบวมมากเกินไป ผมขอเสนอให้คุณว่าหากต้องการลดขนาดโค้ดให้เล็กที่สุดโดยการย่อโค้ด, ประสิทธิภาพ, การอ่านโค้ด, การจัดการโปรเจกต์ขนาดใหญ่, และการแนะนำไวยากรณ์ในตัวแก้ไขโค้ดหลายตัว, นี่คือวิธีที่ถูกต้องในการสร้าง enumerations: ตัวแปรแบบ underscore-notation.


ตามที่แสดงในแผนภาพข้างต้นและตัวอย่างด้านล่าง นี่คือห้าวิธีง่ายๆ ในการเริ่มต้น:

  1. กำหนดชื่อสำหรับกลุ่มการระบุค่า คิดหาคำนามที่สามารถอธิบายวัตถุประสงค์ของการระบุค่าหรืออย่างน้อยก็รายการที่อยู่ในกลุ่มการระบุค่านั้น ตัวอย่างเช่น กลุ่มการระบุค่าที่แทนสีที่ผู้ใช้สามารถเลือกได้ อาจจะตั้งชื่อว่า COLORCHOICES จะเหมาะสมกว่าการตั้งชื่อว่า COLORS
  2. ตัดสินว่าการนับรายการในกลุ่มเป็นแบบแยกตัวออกจากกันหรือเป็นอิสระ หากเป็นแบบแยกตัวออกจากกัน ให้เริ่มชื่อของตัวแปรที่นับรายการแต่ละตัวด้วย ENUM_ หากเป็นแบบอิสระหรือข้างกัน ให้ใช้ INDEX_
  3. สำหรับแต่ละรายการ ให้สร้างตัวแปรท้องถิ่นใหม่ที่มีชื่อขึ้นต้นด้วย ENUM_ หรือ INDEX_ ตามด้วยชื่อของกลุ่ม ตามด้วยเครื่องหมายขีดล่าง แล้วต่อด้วยชื่อที่เป็นมิตรและไม่ซ้ำสำหรับคุณสมบัติ
  4. เพิ่มตัวแปรนับจำนวน ENUMLENGTH_, ENUMLEN_, INDEXLENGTH_, หรือ INDEXLEN_ (ไม่ว่าจะเป็น LEN_ หรือ LENGTH_ ขึ้นอยู่กับความชอบส่วนตัว) ในตอนท้ายสุด คุณควรใช้ตัวแปรนี้ทุกที่ที่เป็นไปได้ในโค้ดของคุณเพื่อให้มั่นใจว่าการเพิ่มรายการเพิ่มเติมใน enumeration และเพิ่มค่าของตัวแปรนี้จะไม่ทำให้โค้ดของคุณเสียหาย
  5. ให้ค่าที่เรียงตามตัวแปรแบบนับแต่ละตัวมากกว่าตัวก่อนหน้าโดยเริ่มที่ 0 มีคอมเมนต์บนหน้านี้ที่บอกว่า 0 ไม่ควรถูกใช้เป็นค่าที่นับเพราะว่า 0 == null , 0 == false , 0 == "" และความบ้าของ JS อื่น ๆ ผมขอเสนอว่าการเพื่อหลีกเลี่ยงปัญหานี้และเพิ่มประสิทธิภาพในเวลาเดียวกัน ให้ใช้ === เสมอและอย่าให้ == ปรากฏในโค้ดของคุณยกเว้นกับ typeof (เช่น typeof X == "string") ตลอดหลายปีที่ผมใช้ === ผมไม่เคยเจอปัญหาใด ๆ ในการใช้ 0 เป็นค่าการนับเลย ถ้าคุณยังรู้สึกไม่สบายใจ 1 สามารถถูกใช้เป็นค่าตั้งต้นในการนับประเภท ENUM_ (แต่ไม่ใช่กับ INDEX_) โดยไม่มีค่าบาปด้านประสิทธิภาพในหลายกรณี
const ENUM_COLORENUM_RED   = 0;
const ENUM_COLORENUM_GREEN = 1;
const ENUM_COLORENUM_BLUE  = 2;
const ENUMLEN_COLORENUM    = 3;

// later on

if(currentColor === ENUM_COLORENUM_RED) {
   // whatever
}

นี่คือวิธีที่ฉันจำว่าเมื่อไหร่ควรใช้ INDEX_ และเมื่อไหร่ควรใช้ ENUM_:

// Precondition: var arr = []; //
arr[INDEX_] = ENUM_;

อย่างไรก็ตาม ENUM_ สามารถในบางกรณีเหมาะสมที่จะใช้เป็นดัชนี เช่นเมื่อกำลังนับจำนวนครั้งที่ปรากฏของแต่ละรายการ

const ENUM_PET_CAT = 0,
      ENUM_PET_DOG = 1,
      ENUM_PET_RAT = 2,
      ENUMLEN_PET  = 3;

var favoritePets = [ENUM_PET_CAT, ENUM_PET_DOG, ENUM_PET_RAT,
                    ENUM_PET_DOG, ENUM_PET_DOG, ENUM_PET_CAT,
                    ENUM_PET_RAT, ENUM_PET_CAT, ENUM_PET_DOG];

var petsFrequency = [];

for (var i=0; i<ENUMLEN_PET; i=i+1|0)
  petsFrequency[i] = 0;

for (var i=0, len=favoritePets.length|0, petId=0; i<len; i=i+1|0)
  petsFrequency[petId = favoritePets[i]|0] = (petsFrequency[petId]|0) + 1|0;

console.log({
    "cat": petsFrequency[ENUM_PET_CAT],
    "dog": petsFrequency[ENUM_PET_DOG],
    "rat": petsFrequency[ENUM_PET_RAT]
});

สังเกตได้ว่า ในโค้ดข้างต้น การเพิ่มสัตว์เลี้ยงชนิดใหม่ทำได้ง่ายมาก: คุณเพียงแค่เพิ่มรายการใหม่หลัง ENUM_PET_RAT และอัปเดต ENUMLEN_PET ตามลำดับ การเพิ่มรายการใหม่ในระบบการนับอื่น ๆ อาจยากและมีข้อบกพร่องมากกว่า


ขยายตัวแปรตัวพิมพ์ใหญ่ด้วยการบวก

นอกจากนี้ ไวยากรณ์ของการระบุประเภทนี้ยังอนุญาตให้การขยายคลาสมีความชัดเจนและกระชับดังที่เห็นด้านล่าง ในการขยายคลาส ให้เพิ่มหมายเลขที่เพิ่มขึ้นไปยังรายการ LEN_ ของคลาสแม่ จากนั้นจบคลาสย่อยด้วยรายการ LEN_ ของตัวเอง เพื่อให้คลาสย่อยสามารถขยายต่อไปได้ในอนาคต

(function(window){
    "use strict";
    var parseInt = window.parseInt;

    // use INDEX_ when representing the index in an array instance
    const INDEX_PIXELCOLOR_TYPE = 0, // is a ENUM_PIXELTYPE
          INDEXLEN_PIXELCOLOR   = 1,
          INDEX_SOLIDCOLOR_R    = INDEXLEN_PIXELCOLOR+0,
          INDEX_SOLIDCOLOR_G    = INDEXLEN_PIXELCOLOR+1,
          INDEX_SOLIDCOLOR_B    = INDEXLEN_PIXELCOLOR+2,
          INDEXLEN_SOLIDCOLOR   = INDEXLEN_PIXELCOLOR+3,
          INDEX_ALPHACOLOR_R    = INDEXLEN_PIXELCOLOR+0,
          INDEX_ALPHACOLOR_G    = INDEXLEN_PIXELCOLOR+1,
          INDEX_ALPHACOLOR_B    = INDEXLEN_PIXELCOLOR+2,
          INDEX_ALPHACOLOR_A    = INDEXLEN_PIXELCOLOR+3,
          INDEXLEN_ALPHACOLOR   = INDEXLEN_PIXELCOLOR+4,
    // use ENUM_ when representing a mutually-exclusive species or type
          ENUM_PIXELTYPE_SOLID = 0,
          ENUM_PIXELTYPE_ALPHA = 1,
          ENUM_PIXELTYPE_UNKNOWN = 2,
          ENUMLEN_PIXELTYPE    = 2;

    function parseHexColor(inputString) {
        var rawstr = inputString.trim().substring(1);
        var result = [];
        if (rawstr.length === 8) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
            result[INDEX_ALPHACOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[INDEX_ALPHACOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[INDEX_ALPHACOLOR_B] = parseInt(rawstr.substring(4,6), 16);
            result[INDEX_ALPHACOLOR_A] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 4) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_ALPHA;
            result[INDEX_ALPHACOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[INDEX_ALPHACOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[INDEX_ALPHACOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
            result[INDEX_ALPHACOLOR_A] = parseInt(rawstr[3], 16) * 0x11;
        } else if (rawstr.length === 6) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[INDEX_SOLIDCOLOR_R] = parseInt(rawstr.substring(0,2), 16);
            result[INDEX_SOLIDCOLOR_G] = parseInt(rawstr.substring(2,4), 16);
            result[INDEX_SOLIDCOLOR_B] = parseInt(rawstr.substring(4,6), 16);
        } else if (rawstr.length === 3) {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_SOLID;
            result[INDEX_SOLIDCOLOR_R] = parseInt(rawstr[0], 16) * 0x11;
            result[INDEX_SOLIDCOLOR_G] = parseInt(rawstr[1], 16) * 0x11;
            result[INDEX_SOLIDCOLOR_B] = parseInt(rawstr[2], 16) * 0x11;
        } else {
            result[INDEX_PIXELCOLOR_TYPE] = ENUM_PIXELTYPE_UNKNOWN;
        }
        return result;
    }

    // the red component of green
    console.log(parseHexColor("#0f0")[INDEX_SOLIDCOLOR_R]);
    // the alpha of transparent purple
    console.log(parseHexColor("#f0f7")[INDEX_ALPHACOLOR_A]); 
    // the enumerated array for turquoise
    console.log(parseHexColor("#40E0D0"));
})(self);

(ความยาว: 2,450 ไบต์)

บางคนอาจจะบอกว่านี่ไม่ค่อยใช้งานได้จริงเท่าโซลูชันอื่น: มันเปลืองพื้นที่มาก ใช้เวลานานในการเขียน และไม่ได้เคลือบด้วยไวยากรณ์ที่สวยงาม คนเหล่านั้นจะถูกถ้าหากพวกเขาไม่ย่อรหัสของพวกเขา แต่ไม่มีคนมีเหตุผลคนไหนที่จะทิ้งรหัสที่ไม่ถูกย่อไว้ในผลิตภัณฑ์สุดท้าย สำหรับการย่อนี้ Closure Compiler เป็นสิ่งที่ดีที่สุดที่ฉันเคยพบ การเข้าถึงออนไลน์สามารถพบได้ที่นี่ Closure Compiler สามารถเอาข้อมูลการระบุเหล่านี้ทั้งหมดมาใส่รวมกัน ทำให้ Javascript ของคุณเล็กสุด ๆ และรันเร็วสุด ๆ ดังนั้น จึงควรย่อด้วย Closure Compiler สังเกตดู


ย่อโค้ดด้วย Closure Compiler

คอมไพเลอร์ Closure สามารถทำการปรับแต่งที่น่าอัศจรรย์บางอย่างได้โดยใช้การอนุมานซึ่งเกินความสามารถของตัวลดขนาด Javascript อื่น ๆ คอมไพเลอร์ Closure สามารถใส่ตัวแปร primitive ที่กำหนดค่าคงที่โดยตรงลงในโค้ดได้ คอมไพเลอร์ Closure ยังสามารถทำการอนุมานโดยอิงจากค่าที่ถูกใส่ลงไปเหล่านี้และกำจัดบล็อกที่ไม่ได้ใช้ใน if-statements และ loops ได้อีกด้วย

'use strict';(function(e){function d(a){a=a.trim().substring(1);var b=[];8===a.length?(b[0]=1,b[1]=c(a.substring(0,2),16),b[2]=c(a.substring(2,4),16),b[3]=c(a.substring(4,6),16),b[4]=c(a.substring(4,6),16)):4===a.length?(b[1]=17*c(a[0],16),b[2]=17*c(a[1],16),b[3]=17*c(a[2],16),b[4]=17*c(a[3],16)):6===a.length?(b[0]=0,b[1]=c(a.substring(0,2),16),b[2]=c(a.substring(2,4),16),b[3]=c(a.substring(4,6),16)):3===a.length?(b[0]=0,b[1]=17*c(a[0],16),b[2]=17*c(a[1],16),b[3]=17*c(a[2],16)):b[0]=2;return b}var c=
e.parseInt;console.log(d("#0f0")[1]);console.log(d("#f0f7")[4]);console.log(d("#40E0D0"))})(self);

(ความยาว: 605 ไบต์)

Closure Compiler ให้รางวัลคุณสำหรับการเขียนโค้ดอย่างชาญฉลาดและจัดระเบียบโค้ดของคุณได้ดี เพราะในขณะที่หลายโปรแกรมย่อโค้ด (minifier) มักจะลงโทษโค้ดที่จัดระเบียบแล้วด้วยขนาดไฟล์ที่ย่อแล้วใหญ่ขึ้น Closure Compiler กลับสามารถกรองความสะอาดและความมีเหตุผลทั้งหมดของคุณเพื่อสร้างไฟล์ที่มีขนาดเล็กลงได้มากขึ้นหากคุณใช้เทคนิคอย่างการระบุชื่อตัวแปร ด้วยเหตุนี้ สำหรับผู้หนึ่ง นี่คือสิ่งศักดิ์สิทธิ์ของการเขียนโปรแกรม: เครื่องมือที่ทั้งช่วยให้โค้ดของคุณมีขนาดไฟล์ย่อที่เล็กลงและช่วยฝึกฝนจิตใจของคุณให้มีนิสัยการเขียนโปรแกรมที่ดีขึ้น


ขนาดโค้ดเล็กลง

ตอนนี้ มาดูกันว่าไฟล์ที่เทียบเท่าจะมีขนาดใหญ่แค่ไหนหากไม่มีการนับเหล่านี้เลย

ซอร์สโค้ดโดยไม่ใช้การระบุลำดับ (ความยาว: 1,973 ไบต์ (สั้นกว่ารหัสที่ระบุลำดับ 477 ไบต์!))
ซอร์สโค้ดย่อโดยไม่ใช้การระบุลำดับ (ความยาว: 843 ไบต์ (ยาวกว่ารหัสที่ระบุลำดับ 238 ไบต์ ))

ดังที่เห็น โดยไม่มีการระบุลำดับ โค้ดต้นฉบับจะสั้นลง แต่ต้องเสียพื้นที่ของโค้ดมินิไฟด์ที่ใหญ่กว่า ฉันไม่ทราบเกี่ยวกับคุณ แต่ฉันแน่ใจว่าฉันไม่รวมโค้ดต้นฉบับเข้าไปในผลิตภัณฑ์สุดท้าย ดังนั้น รูปแบบการระบุลำดับนี้จึงเหนือกว่าเพราะทำให้ขนาดไฟล์มินิไฟด์เล็กลง


การแก้ไขข้อบกพร่องแบบร่วมมือ 🤝

ข้อได้เปรียบอีกอย่างเกี่ยวกับรูปแบบการนับนี้คือสามารถใช้เพื่อจัดการโครงการขนาดใหญ่ได้อย่างง่ายดายโดยไม่เสียขนาดโค้ดที่ย่อ เมื่อทำงานในโครงการขนาดใหญ่ที่มีคนจำนวนมาก อาจเป็นประโยชน์ที่จะแท็กและระบุชื่อตัวแปรอย่างชัดเจนว่าผู้ใดสร้างโค้ดนั้น เพื่อให้สามารถระบุผู้สร้างโค้ดเดิมได้อย่างรวดเร็วสำหรับการแก้ไขบั๊กแบบร่วมมือ

// JG = Jack Giffin
const ENUM_JG_COLORENUM_RED   = 0,
      ENUM_JG_COLORENUM_GREEN = 1,
      ENUM_JG_COLORENUM_BLUE  = 2,
      ENUMLEN_JG_COLORENUM    = 3;

// later on

if(currentColor === ENUM_JG_COLORENUM_RED) {
   // whatever
}

// PL = Pepper Loftus
// BK = Bob Knight
const ENUM_PL_ARRAYTYPE_UNSORTED   = 0,
      ENUM_PL_ARRAYTYPE_ISSORTED   = 1,
      ENUM_BK_ARRAYTYPE_CHUNKED    = 2, // added by Bob Knight
      ENUM_JG_ARRAYTYPE_INCOMPLETE = 3, // added by jack giffin
      ENUMLEN_PL_COLORENUM         = 4;

// later on

if(
  randomArray === ENUM_PL_ARRAYTYPE_UNSORTED ||
  randomArray === ENUM_BK_ARRAYTYPE_CHUNKED
) {
   // whatever
}

นอกจากนี้ รูปแบบของการนับจำนวนนี้ยังเร็วกว่ามากหลังจากการทำมินิฟิเคชัน ในคุณสมบัติที่มีชื่อปกติ เบราว์เซอร์ต้องใช้แฮชแมปเพื่อตรวจสอบว่าคุณสมบัติอยู่ตรงไหนบนวัตถุ แม้ว่าคอมไพเลอร์ JIT จะเก็บแคชตำแหน่งนี้บนวัตถุอย่างชาญฉลาด แต่ก็ยังมีค่าใช้จ่ายที่สูงมากเนื่องจากกรณีพิเศษ เช่น การลบคุณสมบัติที่มีลำดับต่ำกว่าออกจากวัตถุ

แต่ด้วยอาร์เรย์ PACKED_ELEMENTS ที่มีดัชนีจำนวนเต็มต่อเนื่องแบบไม่กระจัดกระจาย เบราว์เซอร์สามารถข้ามส่วนใหญ่ของค่าใช้จ่ายนั้นได้ เพราะดัชนีของค่าภายในอาร์เรย์ได้ระบุไว้แล้ว ใช่ ตามมาตรฐาน ECMAScript ทุกคุณสมบัติควรถูกปฏิบัติเหมือนเป็นสตริง อย่างไรก็ตาม ด้านนี้ของมาตรฐาน ECMAScript ทำให้เกิดความเข้าใจผิดเกี่ยวกับประสิทธิภาพอย่างมาก เพราะเบราว์เซอร์ทั้งหมดมีการปรับแต่งพิเศษสำหรับดัชนีตัวเลขในอาร์เรย์

/// Hashmaps are slow, even with JIT juice
var ref = {};
ref.count = 10;
ref.value = "foobar";

เปรียบเทียบโค้ดด้านบนกับโค้ดด้านล่าง

/// Arrays, however, are always lightning fast
const INDEX_REFERENCE_COUNT = 0;
const INDEX_REFERENCE_VALUE = 1;
const INDEXLENGTH_REFERENCE = 2;

var ref = [];
ref[INDEX_REFERENCE_COUNT] = 10;
ref[INDEX_REFERENCE_VALUE] = "foobar";

บางคนอาจคัดค้านโค้ดที่มีการกำหนดค่าด้วย enumeration ซึ่งดูเหมือนจะยาวกว่าการใช้วัตถุปกติ แต่รูปลักษณ์อาจหลอกได้ สิ่งสำคัญคือต้องจำไว้ว่า ขนาดของซอร์สโค้ดไม่ได้เป็นสัดส่วนกับขนาดของเอาต์พุตเมื่อใช้ Epic Closure Compiler สังเกตดู

/// Hashmaps are slow, even with JIT juice
var a={count:10,value:"foobar"};

โค้ดที่ย่อโดยไม่มีการระบุรายการอยู่ด้านบน และโค้ดที่ย่อโดยมีการระบุรายการอยู่ด้านล่าง

/// Arrays, however, are always lightning fast
var a=[10,"foobar"];

ตัวอย่างข้างต้นแสดงให้เห็นว่า นอกจากจะมีประสิทธิภาพที่เหนือกว่าแล้ว โค้ดที่ระบุรายการยังส่งผลให้ขนาดไฟล์ที่ย่อเล็กลงด้วย


การดีบักง่ายๆ

นอกจากนี้ สิ่งที่ทำให้สิ่งนี้พิเศษยิ่งขึ้นคือการใช้รูปแบบการเรียงรายการนี้พร้อมกับโปรแกรมแก้ไขข้อความ CodeMirror ในโหมด Javascript โหมดเน้นไวยากรณ์ Javascript ของ CodeMirror จะเน้นตัวแปรท้องถิ่นในสโคปปัจจุบัน ด้วยวิธีนี้ คุณจะรู้ทันทีเมื่อพิมพ์ชื่อตัวแปรถูกต้อง เพราะถ้าชื่อตัวแปรถูกประกาศก่อนหน้านี้ด้วยคีย์เวิร์ด var ชื่อตัวแปรจะเปลี่ยนเป็นสีพิเศษ (ค่าเริ่มต้นเป็นสีฟ้า) แม้ว่าคุณจะไม่ใช้ CodeMirror อย่างน้อยเบราว์เซอร์ก็จะโยนข้อผิดพลาด [variable name] is not defined เมื่อรันโค้ดที่ชื่อตัวแปรเรียงรายการพิมพ์ผิด นอกจากนี้ เครื่องมือ JavaScript เช่น JSLint และ Closure Compiler จะเตือนคุณดังมากเมื่อคุณพิมพ์ชื่อตัวแปรเรียงรายการผิด CodeMirror, เบราว์เซอร์ และเครื่องมือ JavaScript ต่างๆ ร่วมกันทำให้การดีบั๊กการเรียงรายการรูปแบบนี้ง่ายและสะดวกมาก

const ENUM_COLORENUM_RED   = 0,
      ENUM_COLORENUM_GREEN = 1,
      ENUM_COLORENUM_BLUE  = 2,
      ENUMLEN_COLORENUM    = 3;
var currentColor = ENUM_COLORENUM_GREEN;

if(currentColor === ENUM_COLORENUM_RED) {
   // whatever
}

if(currentColor === ENUM_COLORENUM_DNE) {
   // whatever
}

ในตัวอย่างข้างต้น คุณได้รับการแจ้งเตือนข้อผิดพลาดเนื่องจาก ENUM_COLORENUM_DNE ไม่มีอยู่


ข้อสรุป

ฉันคิดว่ามันปลอดภัยที่จะกล่าวว่าวิธีการนับจำนวนนี้เป็นวิธีที่ดีที่สุดไม่ใช่เพียงสำหรับขนาดโค้ดที่ย่อแล้ว แต่ยังสำหรับประสิทธิภาพ การดีบัก และการทำงานร่วมกันด้วย

คำตอบ #8

ใช้พร็อกซีของจาวาสคริปต์

สรุปสั้น: เพิ่มคลาสนี้ไปยังเมธอดยูทิลิตี้ของคุณและใช้งานทั่วทั้งโค้ดของคุณ มันจำลองพฤติกรรมของ Enum จากภาษาการเขียนโปรแกรมแบบดั้งเดิม และจะแจ้งข้อผิดพลาดจริง ๆ เมื่อคุณพยายามเข้าถึง enumerator ที่ไม่มีอยู่หรือเพิ่ม/ปรับปรุง enumerator ไม่จำเป็นต้องพึ่งพา Object.freeze()

class Enum {
  constructor(enumObj) {
    const handler = {
      get(target, name) {
        if (typeof target[name] != 'undefined') {
          return target[name];
        }
        throw new Error(`No such enumerator: ${name}`);
      },
      set() {
        throw new Error('Cannot add/update properties on an Enum instance after it is defined')
      }
    };

    return new Proxy(enumObj, handler);
  }
}

จากนั้นสร้าง enums โดยการสร้างอินสแตนซ์ของคลาส:

const roles = new Enum({
  ADMIN: 'Admin',
  USER: 'User',
});

คำอธิบายอย่างเต็ม

คุณสมบัติที่เป็นประโยชน์มากอย่างหนึ่งของ Enum ที่คุณได้รับจากภาษาดั้งเดิมคือมันจะแจ้งข้อผิดพลาดในขณะคอมไพล์ถ้าคุณพยายามเข้าถึงตัวระบุที่ไม่มีอยู่

นอกจากการทำให้โครงสร้าง enum ที่ถูกล้อเย็นตัวเพื่อป้องกันไม่ให้มีการเพิ่มค่าที่ไม่ตั้งใจหรือเป็นอันตรายแล้ว ไม่มีคำตอบอื่นใดที่กล่าวถึงคุณสมบัติภายในนั้นของ Enums

อย่างที่คุณน่าจะทราบ การเข้าถึงสมาชิกที่ไม่มีอยู่ใน JavaScript จะคืนค่า undefined และจะไม่ทำให้โค้ดของคุณล้มเหลว เนื่องจาก enumerators เป็นค่าคงที่ที่ถูกกำหนดล่วงหน้า (เช่น วันในสัปดาห์) จึงไม่ควรมีกรณีที่ enumerator จะเป็น undefined

อย่าเข้าใจฉันผิด พฤติกรรมของ JavaScript ที่คืนค่า undefined เมื่อเข้าถึงคุณสมบัติที่ไม่ได้กำหนดจริงๆ แล้วเป็นคุณสมบัติที่ทรงพลังของภาษา แต่ก็ไม่ใช่คุณสมบัติที่คุณต้องการเมื่อคุณกำลังพยายามจำลองโครงสร้าง Enum แบบดั้งเดิม

นี่คือจุดที่วัตถุ Proxy โดดเด่น Proxy ถูกมาตรฐานในภาษาเมื่อมีการแนะนำ ES6 (ES2015) นี่คือคำอธิบายจาก MDN:

วัตถุ Proxy ถูกใช้เพื่อกำหนดพฤติกรรมที่กำหนดเองสำหรับการดำเนินการพื้นฐาน (เช่น การค้นหาคุณสมบัติ การกำหนดค่า การนับรายการ การเรียกใช้ฟังก์ชัน เป็นต้น)

คล้ายกับพร็อกซีของเว็บเซิร์ฟเวอร์ พร็อกซีใน JavaScript สามารถสกัดกั้นการดำเนินการบนออบเจ็กต์ได้ (ด้วยการใช้ "traps" หากต้องการเรียกว่า hooks ก็ได้) และอนุญาตให้คุณทำการตรวจสอบ การกระทำ และ/หรือการปรับแต่งต่าง ๆ ก่อนที่การดำเนินการเหล่านั้นจะเสร็จสิ้น (หรือในบางกรณีหยุดการดำเนินการทั้งหมด ซึ่งตรงกับสิ่งที่เราต้องการทำเมื่อเราพยายามอ้างอิงตัวนับที่ไม่มีอยู่)

นี่คือตัวอย่างที่สร้างขึ้นโดยใช้อ็อบเจ็กต์ Proxy เพื่อเลียนแบบ Enum ตัวนับในตัวอย่างนี้คือมาตรฐาน HTTP Methods (เช่น "GET", "POST" เป็นต้น):

// Class for creating enums (13 lines)
// Feel free to add this to your utility library in 
// your codebase and profit! Note: As Proxies are an ES6 
// feature, some browsers/clients may not support it and 
// you may need to transpile using a service like babel

class Enum {
  // The Enum class instantiates a JavaScript Proxy object.
  // Instantiating a `Proxy` object requires two parameters, 
  // a `target` object and a `handler`. We first define the handler,
  // then use the handler to instantiate a Proxy.

  // A proxy handler is simply an object whose properties
  // are functions which define the behavior of the proxy 
  // when an operation is performed on it. 
  
  // For enums, we need to define behavior that lets us check what enumerator
  // is being accessed and what enumerator is being set. This can be done by 
  // defining "get" and "set" traps.
  constructor(enumObj) {
    const handler = {
      get(target, name) {
        if (typeof target[name] != 'undefined') {
          return target[name]
        }
        throw new Error(`No such enumerator: ${name}`)
      },
      set() {
        throw new Error('Cannot add/update properties on an Enum instance after it is defined')
      }
    }


    // Freeze the target object to prevent modifications
    return new Proxy(enumObj, handler)
  }
}


// Now that we have a generic way of creating Enums, lets create our first Enum!
const httpMethods = new Enum({
  DELETE: "DELETE",
  GET: "GET",
  OPTIONS: "OPTIONS",
  PATCH: "PATCH",
  POST: "POST",
  PUT: "PUT"
})

// Sanity checks
console.log(httpMethods.DELETE)
// logs "DELETE"

try {
  httpMethods.delete = "delete"
} catch (e) {
console.log("Error: ", e.message)
}
// throws "Cannot add/update properties on an Enum instance after it is defined"

try {
  console.log(httpMethods.delete)
} catch (e) {
  console.log("Error: ", e.message)
}
// throws "No such enumerator: delete"

เสริม: พร็อกซี่คืออะไรกันแน่?

ฉันจำได้ว่าเมื่อฉันเริ่มเห็นคำว่า 'พร็อกซี' ทุกที่ มันก็ตลกและไม่เข้าใจสำหรับฉันเป็นเวลานาน ถ้าคุณกำลังรู้สึกแบบนั้นตอนนี้ ฉันคิดว่าวิธีที่ง่ายในการทำความเข้าใจกับพร็อกซีคือคิดว่ามันเป็นซอฟต์แวร์ สถาบัน หรือแม้แต่คนที่ทำหน้าที่เป็นตัวกลางระหว่างเซิร์ฟเวอร์ บริษัท หรือคนสองฝ่าย

คำตอบ #9

ฉันเล่นสนุกกับสิ่งนี้อยู่ เพราะฉันชอบ enum ของฉัน =)

โดยใช้ Object.defineProperty ฉันคิดว่าฉันได้คิดค้นวิธีแก้ปัญหาที่พอใช้ได้บางส่วน

นี่คือลิงก์ jsfiddle: http://jsfiddle.net/ZV4A6/

การใช้วิธีนี้.. คุณควรจะสามารถ (ตามทฤษฎี) เรียกและกำหนดค่าของ enum สำหรับวัตถุใดๆ ได้ โดยไม่ส่งผลกระทบต่อคุณลักษณะอื่นของวัตถุนั้น

Object.defineProperty(Object.prototype,'Enum', {
    value: function() {
        for(i in arguments) {
            Object.defineProperty(this,arguments[i], {
                value:parseInt(i),
                writable:false,
                enumerable:true,
                configurable:true
            });
        }
        return this;
    },
    writable:false,
    enumerable:false,
    configurable:false
}); 

เนื่องจากคุณสมบัติ writable:false สิ่งนี้ ควร ทำให้มันปลอดภัยต่อชนิดข้อมูล

ดังนั้นคุณควรสามารถสร้างวัตถุที่กำหนดเอง จากนั้นเรียกใช้งาน Enum() บนมัน ค่าเหล่านี้จะเริ่มต้นที่ 0 และเพิ่มขึ้นตามแต่ละรายการ

var EnumColors={};
EnumColors.Enum('RED','BLUE','GREEN','YELLOW');
EnumColors.RED;    // == 0
EnumColors.BLUE;   // == 1
EnumColors.GREEN;  // == 2
EnumColors.YELLOW; // == 3

คำตอบ #10

นี่เป็นเรื่องเก่าที่ฉันรู้จัก แต่สิ่งที่เกิดขึ้นตั้งแต่นั้นผ่านการใช้งานโดยอินเทอร์เฟซ TypeScript คือ:

var MyEnum;
(function (MyEnum) {
    MyEnum[MyEnum["Foo"] = 0] = "Foo";
    MyEnum[MyEnum["FooBar"] = 2] = "FooBar";
    MyEnum[MyEnum["Bar"] = 1] = "Bar";
})(MyEnum|| (MyEnum= {}));

สิ่งนี้ช่วยให้คุณสามารถค้นหาทั้ง MyEnum.Bar ซึ่งคืนค่า 1 และ MyEnum[1] ซึ่งคืนค่า "Bar" ไม่ว่าจะเป็นลำดับการประกาศใดๆ

คำตอบ #11

ใน ES7 คุณสามารถสร้าง ENUM ที่สวยงามโดยอาศัยคุณสมบัติแบบสแตติก:

class ColorEnum  {
    static RED = 0 ;
    static GREEN = 1;
    static BLUE = 2;
}

แล้ว

if (currentColor === ColorEnum.GREEN ) {/*-- coding --*/}

ข้อดี (ของการใช้คลาสแทนการใช้วัตถุโดยตรง) คือการมีคลาสแม่ Enum จากนั้น Enum ทั้งหมดของคุณจะ สืบทอด คลาสนั้นได้

 class ColorEnum  extends Enum {/*....*/}

คำตอบ #12

สร้างอ็อบเจ็กต์ลิตเทอรัล:

const Modes = {
  DRAGGING: 'drag',
  SCALING:  'scale',
  CLICKED:  'click'
};

คำตอบ #13

นี่คือวิธีแก้ปัญหาที่ฉันใช้

function Enum() {
    this._enums = [];
    this._lookups = {};
}

Enum.prototype.getEnums = function() {
    return _enums;
}

Enum.prototype.forEach = function(callback){
    var length = this._enums.length;
    for (var i = 0; i < length; ++i){
        callback(this._enums[i]);
    }
}

Enum.prototype.addEnum = function(e) {
    this._enums.push(e);
}

Enum.prototype.getByName = function(name) {
    return this[name];
}

Enum.prototype.getByValue = function(field, value) {
    var lookup = this._lookups[field];
    if(lookup) {
        return lookup[value];
    } else {
        this._lookups[field] = ( lookup = {});
        var k = this._enums.length - 1;
        for(; k >= 0; --k) {
            var m = this._enums[k];
            var j = m[field];
            lookup[j] = m;
            if(j == value) {
                return m;
            }
        }
    }
    return null;
}

function defineEnum(definition) {
    var k;
    var e = new Enum();
    for(k in definition) {
        var j = definition[k];
        e[k] = j;
        e.addEnum(j)
    }
    return e;
}

และคุณกำหนดค่า enums ของคุณแบบนี้:

var COLORS = defineEnum({
    RED : {
        value : 1,
        string : 'red'
    },
    GREEN : {
        value : 2,
        string : 'green'
    },
    BLUE : {
        value : 3,
        string : 'blue'
    }
});

และนี่คือวิธีการเข้าถึงอีนัมของคุณ:

COLORS.BLUE.string
COLORS.BLUE.value
COLORS.getByName('BLUE').string
COLORS.getByValue('value', 1).string

COLORS.forEach(function(e){
    // do what you want with e
});

ฉันมักจะใช้วิธีสุดท้าย 2 วิธีในการแมปอีนัมจากอ็อบเจ็กต์ข้อความ

ข้อดีบางประการของแนวทางนี้:

  • ง่ายต่อการประกาศ enums
  • เข้าถึงตัวแปร enum ของคุณได้ง่าย
  • อีนัมของคุณสามารถเป็นประเภทซับซ้อนได้
  • คลาส Enum มีการเก็บแคชแบบเชื่อมโยงบางส่วนถ้าคุณใช้ getByValue บ่อยๆ

ข้อเสียบางประการ:

  • มีการจัดการหน่วยความจำที่ยุ่งเหยิงอยู่บ้างที่นั่น เนื่องจากฉันต้องเก็บการอ้างอิงไปยังอีนัม
  • ยังคงไม่มีความปลอดภัยของประเภท

คำตอบ #14

ถ้าคุณใช้ Backbone คุณสามารถใช้งานฟังก์ชัน enum เต็มรูปแบบ (ค้นหาด้วย id, ชื่อ, สมาชิกที่กำหนดเอง) ได้ฟรีโดยใช้ Backbone.Collection

// enum instance members, optional
var Color = Backbone.Model.extend({
    print : function() {
        console.log("I am " + this.get("name"))
    }
});

// enum creation
var Colors = new Backbone.Collection([
    { id : 1, name : "Red", rgb : 0xFF0000},
    { id : 2, name : "Green" , rgb : 0x00FF00},
    { id : 3, name : "Blue" , rgb : 0x0000FF}
], {
    model : Color
});

// Expose members through public fields.
Colors.each(function(color) {
    Colors[color.get("name")] = color;
});

// using
Colors.Red.print()

บทความที่เกี่ยวข้อง