ฉันจะรับประกันได้อย่างไรว่าคำจำกัดความของ 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.
ตามที่แสดงในแผนภาพข้างต้นและตัวอย่างด้านล่าง นี่คือห้าวิธีง่ายๆ ในการเริ่มต้น:
- กำหนดชื่อสำหรับกลุ่มการระบุค่า คิดหาคำนามที่สามารถอธิบายวัตถุประสงค์ของการระบุค่าหรืออย่างน้อยก็รายการที่อยู่ในกลุ่มการระบุค่านั้น ตัวอย่างเช่น กลุ่มการระบุค่าที่แทนสีที่ผู้ใช้สามารถเลือกได้ อาจจะตั้งชื่อว่า COLORCHOICES จะเหมาะสมกว่าการตั้งชื่อว่า COLORS
- ตัดสินว่าการนับรายการในกลุ่มเป็นแบบแยกตัวออกจากกันหรือเป็นอิสระ หากเป็นแบบแยกตัวออกจากกัน ให้เริ่มชื่อของตัวแปรที่นับรายการแต่ละตัวด้วย
ENUM_หากเป็นแบบอิสระหรือข้างกัน ให้ใช้INDEX_ - สำหรับแต่ละรายการ ให้สร้างตัวแปรท้องถิ่นใหม่ที่มีชื่อขึ้นต้นด้วย
ENUM_หรือINDEX_ตามด้วยชื่อของกลุ่ม ตามด้วยเครื่องหมายขีดล่าง แล้วต่อด้วยชื่อที่เป็นมิตรและไม่ซ้ำสำหรับคุณสมบัติ - เพิ่มตัวแปรนับจำนวน
ENUMLENGTH_,ENUMLEN_,INDEXLENGTH_, หรือINDEXLEN_(ไม่ว่าจะเป็นLEN_หรือLENGTH_ขึ้นอยู่กับความชอบส่วนตัว) ในตอนท้ายสุด คุณควรใช้ตัวแปรนี้ทุกที่ที่เป็นไปได้ในโค้ดของคุณเพื่อให้มั่นใจว่าการเพิ่มรายการเพิ่มเติมใน enumeration และเพิ่มค่าของตัวแปรนี้จะไม่ทำให้โค้ดของคุณเสียหาย - ให้ค่าที่เรียงตามตัวแปรแบบนับแต่ละตัวมากกว่าตัวก่อนหน้าโดยเริ่มที่ 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()





