ตัวแปร enum ที่แตกต่างกันทำงานอย่างไรใน TypeScript


116

TypeScript มีหลายวิธีในการกำหนด enum:

enum Alpha { X, Y, Z }
const enum Beta { X, Y, Z }
declare enum Gamma { X, Y, Z }
declare const enum Delta { X, Y, Z }

หากฉันพยายามใช้ค่าจากGammaรันไทม์ฉันได้รับข้อผิดพลาดเนื่องจากGammaไม่ได้กำหนดไว้ แต่นั่นไม่ใช่กรณีของDeltaหรือAlpha? อะไรconstหรือdeclareหมายถึงในการประกาศที่นี่?

นอกจากนี้ยังมีpreserveConstEnumsแฟล็กคอมไพเลอร์ - สิ่งนี้โต้ตอบกับสิ่งเหล่านี้อย่างไร


1
ฉันเพิ่งเขียนบทความเกี่ยวกับเรื่องนี้แม้ว่ามันจะเกี่ยวข้องกับการเปรียบเทียบ const กับ non const enums
มากกว่าก็ตาม

คำตอบ:


247

มีสี่แง่มุมที่แตกต่างกันใน TypeScript ที่คุณต้องระวัง ประการแรกคำจำกัดความบางประการ:

"วัตถุค้นหา"

ถ้าคุณเขียน enum นี้:

enum Foo { X, Y }

TypeScript จะปล่อยวัตถุต่อไปนี้:

var Foo;
(function (Foo) {
    Foo[Foo["X"] = 0] = "X";
    Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));

ฉันจะอ้างถึงนี้เป็นวัตถุค้นหา จุดประสงค์ของมันเป็นสองเท่า: เพื่อทำหน้าที่เป็นการทำแผนที่จากสายการตัวเลขเช่นเมื่อเขียนFoo.XหรือFoo['X'], และการให้บริการการทำแผนที่จากตัวเลขที่จะสาย ว่าการทำแผนที่กลับจะเป็นประโยชน์สำหรับการแก้จุดบกพร่องหรือเข้าสู่ระบบวัตถุประสงค์ - คุณมักจะมีค่า0หรือ1และต้องการที่จะได้รับสตริงที่สอดคล้องกันหรือ"X""Y"

"ประกาศ"หรือ " โดยรอบ "

ใน TypeScript คุณสามารถ "ประกาศ" สิ่งที่คอมไพลเลอร์ควรรู้ แต่ไม่สามารถปล่อยโค้ดออกมาได้ สิ่งนี้มีประโยชน์เมื่อคุณมีไลบรารีเช่น jQuery ที่กำหนดอ็อบเจ็กต์บางอย่าง (เช่น$) ที่คุณต้องการพิมพ์ข้อมูล แต่ไม่ต้องการโค้ดใด ๆ ที่สร้างโดยคอมไพเลอร์ ข้อมูลจำเพาะและเอกสารอื่น ๆ อ้างถึงการประกาศที่ทำในลักษณะนี้ว่าอยู่ในบริบท "แวดล้อม"; สิ่งสำคัญคือต้องทราบว่าการประกาศทั้งหมดใน.d.tsไฟล์เป็น "สภาพแวดล้อม" (อาจต้องมีdeclareตัวปรับแต่งที่ชัดเจนหรือมีโดยปริยายขึ้นอยู่กับประเภทการประกาศ)

"อินไลน์"

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

enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";

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


Enums ทำงานอย่างไร?

ลองแบ่งมันออกตามลักษณะของ enum แต่ละด้าน น่าเสียดายที่แต่ละส่วนในสี่ส่วนนี้จะอ้างอิงคำศัพท์จากส่วนอื่น ๆ ทั้งหมดดังนั้นคุณอาจต้องอ่านข้อมูลทั้งหมดนี้มากกว่าหนึ่งครั้ง

คำนวณเทียบกับไม่คำนวณ (ค่าคงที่)

สมาชิก Enum สามารถคำนวณหรือไม่ก็ได้ ข้อมูลจำเพาะเรียกร้องที่ไม่ได้คำนวณสมาชิกอย่างต่อเนื่องแต่ฉันจะเรียกพวกเขาไม่ใช่การคำนวณความสับสนหลีกเลี่ยงกับconst

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

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

สมาชิกของdeclare enum(เช่น enum โดยรอบ) เป็นค่าคงที่ถ้ามีตัวเริ่มต้นเท่านั้น มิฉะนั้นจะคำนวณ โปรดทราบว่าใน a declare enumอนุญาตให้ใช้ตัวเริ่มต้นที่เป็นตัวเลขเท่านั้น ตัวอย่าง:

declare enum Foo {
    X, // Computed
    Y = 2, // Non-computed
    Z, // Computed! Not 3! Careful!
    Q = 1 + 1 // Error
}

สุดท้ายสมาชิกของ enums ที่ไม่ประกาศ non-const จะถูกพิจารณาให้คำนวณเสมอ อย่างไรก็ตามนิพจน์การเริ่มต้นจะลดลงเหลือเพียงค่าคงที่หากคำนวณได้ในเวลาคอมไพล์ ซึ่งหมายความว่าสมาชิก enum ที่ไม่ใช่ const จะไม่อยู่ในบรรทัด (พฤติกรรมนี้เปลี่ยนไปใน TypeScript 1.5 โปรดดู "การเปลี่ยนแปลงใน TypeScript" ที่ด้านล่าง)

const เทียบกับ non-const

const

การประกาศ enum สามารถมีconstตัวปรับเปลี่ยนได้ ถ้าเป็น enum const, ทั้งหมดอ้างอิงถึงสมาชิก inlined

const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always

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

ไม่ใช่ const

หากการประกาศ enum ไม่มีโมดิconstฟายเออร์การอ้างอิงถึงสมาชิกจะถูกแทรกในกรณีที่สมาชิกไม่ได้คำนวณเท่านั้น enum ที่ไม่ใช่ const และไม่ประกาศจะสร้างอ็อบเจ็กต์การค้นหา

ประกาศ (โดยรอบ) เทียบกับไม่ประกาศ

คำนำที่สำคัญคือว่าdeclareใน typescript มีความหมายที่เฉพาะเจาะจงมาก: วัตถุนี้มีอยู่ที่อื่น ใช้สำหรับอธิบายวัตถุที่มีอยู่ การใช้declareกำหนดวัตถุที่ไม่มีอยู่จริงอาจส่งผลเสียได้ เราจะสำรวจสิ่งเหล่านั้นในภายหลัง

ประกาศ

declare enumจะไม่ปล่อยวัตถุการค้นหา การอ้างอิงถึงสมาชิกจะมีการขีดเส้นใต้หากสมาชิกเหล่านั้นคำนวณ (ดูด้านบนเกี่ยวกับการคำนวณเทียบกับไม่คำนวณ)

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

// Note: Assume no other file has actually created a Foo var at runtime
declare enum Foo { Bar } 
var s = 'Bar';
var b = Foo[s]; // Fails

ข้อผิดพลาดนี้อยู่ในหมวดหมู่ "อย่าโกหกคอมไพเลอร์" หากคุณไม่มีวัตถุชื่อFooรันไทม์อย่าเขียนdeclare enum Foo!

A declare const enumไม่แตกต่างจาก a const enumยกเว้นในกรณีของ --preserveConstEnums (ดูด้านล่าง)

ไม่ใช่ประกาศ

enum constไม่ใช่ประกาศผลิตวัตถุค้นหาถ้ามันไม่ได้ Inlining ได้อธิบายไว้ข้างต้น

ธง --preserveConstEnums

แฟล็กนี้มีเอฟเฟกต์เดียวเท่านั้น: enums ที่ไม่ประกาศจะส่งอ็อบเจ็กต์การค้นหา การซับในไม่ได้รับผลกระทบ สิ่งนี้มีประโยชน์สำหรับการดีบัก


ข้อผิดพลาดทั่วไป

ข้อผิดพลาดที่พบบ่อยที่สุดคือการใช้declare enumเมื่อเป็นประจำenumหรือconst enumจะเหมาะสมกว่า รูปแบบทั่วไปคือ:

module MyModule {
    // Claiming this enum exists with 'declare', but it doesn't...
    export declare enum Lies {
        Foo = 0,
        Bar = 1     
    }
    var x = Lies.Foo; // Depend on inlining
}

module SomeOtherCode {
    // x ends up as 'undefined' at runtime
    import x = MyModule.Lies;

    // Try to use lookup object, which ought to exist
    // runtime error, canot read property 0 of undefined
    console.log(x[x.Foo]);
}

อย่าลืมกฎทอง: ไม่เคยdeclareสิ่งที่ไม่ได้มีอยู่จริง ใช้const enumถ้าคุณต้องการซับในเสมอหรือenumถ้าคุณต้องการให้วัตถุค้นหา


การเปลี่ยนแปลงใน TypeScript

ระหว่าง TypeScript 1.4 และ 1.5 มีการเปลี่ยนแปลงในลักษณะการทำงาน (ดูhttps://github.com/Microsoft/TypeScript/issues/2183 ) เพื่อให้สมาชิกทั้งหมดของ enums ที่ไม่ได้ประกาศ non-const ได้รับการปฏิบัติตามที่คำนวณได้แม้ว่า พวกเขาเริ่มต้นอย่างชัดเจนด้วยตัวอักษร "การแยกทารกออกจากกัน" นี้เพื่อที่จะพูดทำให้พฤติกรรมการฝังในสามารถคาดเดาได้ง่ายขึ้นและแยกแนวคิดของแนวคิดconst enumปกติออกจากปกติenumได้อย่างหมดจด ก่อนที่จะมีการเปลี่ยนแปลงนี้สมาชิกที่ไม่ได้คำนวณของ enums ที่ไม่ได้คำนวณได้ถูกแทรกซึมในเชิงรุกมากขึ้น


6
คำตอบที่ยอดเยี่ยมจริงๆ มันเคลียร์หลายสิ่งหลายอย่างสำหรับฉันไม่ใช่แค่ enums
Clark

1
ฉันหวังว่าฉันจะโหวตให้คุณมากกว่าหนึ่งครั้ง ... ไม่รู้เกี่ยวกับการเปลี่ยนแปลงที่ทำลายล้างนั้น ในการกำหนดเวอร์ชันเชิงความหมายที่เหมาะสมสิ่งนี้อาจถือได้ว่าเป็นการชนกับเวอร์ชันหลัก: - /
mfeineis

การเปรียบเทียบenumประเภทต่างๆที่เป็นประโยชน์มากขอบคุณ!
Marius Schulz

@Ryan นี้มีประโยชน์มากขอบคุณ! ตอนนี้เราต้องการWeb Essentials 2015เพื่อสร้างความเหมาะสมconstสำหรับประเภท enum ที่ประกาศไว้
styfle

19
คำตอบนี้ดูเหมือนจะมีรายละเอียดมากพอที่จะอธิบายสถานการณ์ใน 1.4 แล้วในตอนท้ายมันก็บอกว่า“ แต่ 1.5 เปลี่ยนไปทั้งหมดและตอนนี้มันง่ายกว่ามาก” สมมติว่าฉันเข้าใจสิ่งต่างๆอย่างถูกต้ององค์กรนี้จะไม่เหมาะสมมากขึ้นเรื่อย ๆ เมื่อคำตอบนี้เก่าขึ้น: ฉันขอแนะนำอย่างยิ่งให้ใส่สถานการณ์ปัจจุบันที่ง่ายกว่าก่อนและหลังจากนั้นพูดว่า“ แต่ถ้าคุณใช้ 1.4 หรือก่อนหน้านี้สิ่งต่างๆ ซับซ้อนกว่าเล็กน้อย”
KRyan

33

มีบางสิ่งเกิดขึ้นที่นี่ ไปกันเป็นกรณี ๆ ไป

enum

enum Cheese { Brie, Cheddar }

ประการแรก enum เก่าธรรมดา เมื่อคอมไพล์เป็น JavaScript สิ่งนี้จะแสดงตารางการค้นหา

ตารางการค้นหามีลักษณะดังนี้:

var Cheese;
(function (Cheese) {
    Cheese[Cheese["Brie"] = 0] = "Brie";
    Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));

จากนั้นเมื่อคุณมีCheese.Brieใน typescript ก็ปล่อยออกมาCheese.Brieใน JavaScript ซึ่งประเมิน 0. Cheese[0]ส่งเสียงจริงและประเมินCheese[0]"Brie"

const enum

const enum Bread { Rye, Wheat }

ไม่มีการแสดงรหัสสำหรับสิ่งนี้! ค่าของมันเป็นแบบอินไลน์ ต่อไปนี้จะปล่อยค่า 0 ใน JavaScript:

Bread.Rye
Bread['Rye']

const enumการซับในอาจมีประโยชน์สำหรับเหตุผลด้านประสิทธิภาพ

แต่เรื่องBread[0]อะไร? สิ่งนี้จะผิดพลาดที่รันไทม์และคอมไพเลอร์ของคุณควรจับมัน ไม่มีตารางการค้นหาและคอมไพเลอร์ไม่อยู่ในบรรทัดที่นี่

โปรดทราบว่าในกรณีข้างต้นแฟล็ก --preserveConstEnums จะทำให้ Bread แสดงตารางการค้นหา ค่าของมันจะยังคงอยู่ในบรรทัดเดียว

ประกาศ enum

เช่นเดียวกับคนอื่น ๆ ใช้declare, declareส่งเสียงรหัสและไม่คาดหวังให้คุณได้กำหนดรหัสจริงอื่น ๆ สิ่งนี้ไม่มีตารางการค้นหา:

declare enum Wine { Red, Wine }

Wine.Redส่งออกWine.Redใน JavaScript แต่จะไม่มีตารางการค้นหาไวน์ที่จะอ้างอิงดังนั้นจึงเป็นข้อผิดพลาดเว้นแต่คุณจะกำหนดไว้ที่อื่น

ประกาศ const enum

สิ่งนี้ไม่มีตารางการค้นหา:

declare const enum Fruit { Apple, Pear }

แต่มันอินไลน์! Fruit.Appleส่งเสียง 0 แต่Fruit[0]จะเกิดข้อผิดพลาดอีกครั้งที่รันไทม์เนื่องจากไม่ได้อยู่ในบรรทัดและไม่มีตารางการค้นหา

ผมเคยเขียนเรื่องนี้ขึ้นมาในนี้สนามเด็กเล่น ฉันแนะนำให้เล่นที่นั่นเพื่อทำความเข้าใจว่า TypeScript ใดที่ปล่อย JavaScript ออกมา


1
ฉันขอแนะนำให้อัปเดตคำตอบนี้: ใน typescript 3.3.3 Bread[0]แสดงข้อผิดพลาดของคอมไพเลอร์: “ สมาชิก const enum สามารถเข้าถึงได้โดยใช้สตริงลิเทอรัลเท่านั้น”
chharvey

1
หืม ... มันต่างจากที่ตอบหรือเปล่า? "แล้ว Bread [0] ล่ะสิ่งนี้จะผิดพลาดที่รันไทม์และคอมไพเลอร์ของคุณควรจับได้ไม่มีตารางค้นหาและคอมไพเลอร์ไม่อินไลน์ที่นี่"
กาด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.