Các biến thể enum khác nhau hoạt động như thế nào trong TypeScript?


116

TypeScript có rất nhiều cách khác nhau để định nghĩa một 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 }

Nếu tôi cố gắng sử dụng một giá trị từ Gammatrong thời gian chạy, tôi gặp lỗi vì Gammakhông được xác định, nhưng đó không phải là trường hợp của Deltahoặc Alpha? Các khai báo ở đây có ý nghĩa consthay declaregì?

Ngoài ra còn có một preserveConstEnumscờ trình biên dịch - điều này tương tác với những thứ này như thế nào?


1
Tôi vừa viết một bài báo về điều này , mặc dù nó liên quan nhiều hơn đến việc so sánh const với non const enums
joelmdev

Câu trả lời:


247

Có bốn khía cạnh khác nhau đối với enums trong TypeScript mà bạn cần lưu ý. Đầu tiên, một số định nghĩa:

"đối tượng tra cứu"

Nếu bạn viết enum này:

enum Foo { X, Y }

TypeScript sẽ phát ra đối tượng sau:

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

Tôi sẽ coi đây là đối tượng tra cứu . Mục đích của nó là gấp đôi: phục vụ như một ánh xạ từ chuỗi sang số , ví dụ khi viết Foo.Xhoặc Foo['X'], và phục vụ như một ánh xạ từ số sang chuỗi . Ánh xạ ngược đó rất hữu ích cho các mục đích gỡ lỗi hoặc ghi nhật ký - bạn thường sẽ có giá trị 0hoặc 1và muốn lấy chuỗi tương ứng "X"hoặc "Y".

"khai báo" hoặc " môi trường xung quanh "

Trong TypeScript, bạn có thể "khai báo" những thứ mà trình biên dịch nên biết, nhưng không thực sự phát ra mã. Điều này rất hữu ích khi bạn có các thư viện như jQuery xác định một số đối tượng (ví dụ $) mà bạn muốn nhập thông tin, nhưng không cần bất kỳ mã nào được tạo bởi trình biên dịch. Thông số kỹ thuật và tài liệu khác đề cập đến các khai báo được thực hiện theo cách này như là trong một ngữ cảnh "môi trường xung quanh"; điều quan trọng cần lưu ý là tất cả các khai báo trong .d.tstệp là "môi trường xung quanh" (yêu cầu một công cụ declaresửa đổi rõ ràng hoặc có nó ngầm định, tùy thuộc vào loại khai báo).

"nội tuyến"

Vì lý do hiệu suất và kích thước mã, thường tốt hơn là có tham chiếu đến thành viên enum được thay thế bằng số tương đương của nó khi được biên dịch:

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

Thông số kỹ thuật gọi là sự thay thế này , tôi sẽ gọi nó là nội tuyến vì nó âm thanh mát hơn. Đôi khi bạn sẽ không muốn các thành viên enum được nội dòng, chẳng hạn như vì giá trị enum có thể thay đổi trong phiên bản tương lai của API.


Enums, chúng hoạt động như thế nào?

Hãy chia nhỏ điều này theo từng khía cạnh của enum. Thật không may, mỗi phần trong số bốn phần này sẽ tham chiếu các thuật ngữ từ tất cả các phần khác, vì vậy có thể bạn sẽ cần đọc toàn bộ nội dung này nhiều hơn một lần.

được tính toán và không được tính toán (hằng số)

Thành viên tuyển sinh có thể được tính toán hoặc không. Đặc tả gọi các thành viên không được tính toán là hằng số , nhưng tôi sẽ gọi chúng là không được tính toán để tránh nhầm lẫn với const .

Một thành viên enum được tính toán là một thành viên có giá trị không được biết tại thời điểm biên dịch. Tất nhiên, các tham chiếu đến các thành viên được tính toán không thể được đặt trong dòng. Ngược lại, một thành viên enum không được tính toán đã được biết giá trị của tại thời điểm biên dịch. Các tham chiếu đến các thành viên không tính toán luôn được nội tuyến.

Thành viên enum nào được tính toán và thành viên nào không được tính toán? Đầu tiên, tất cả các thành viên của một constenum là hằng số (tức là không được tính toán), như tên của nó. Đối với một enum không phải const, nó phụ thuộc vào việc bạn đang xem enum xung quanh (khai báo) hay enum không phải ambient.

Thành viên của a declare enum(tức là enum môi trường xung quanh) là không đổi nếu và chỉ khi nó có bộ khởi tạo. Nếu không, nó được tính toán. Lưu ý rằng trong a declare enum, chỉ các bộ khởi tạo số mới được phép. Thí dụ:

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

Cuối cùng, các thành viên của enums không khai báo không const luôn được coi là được tính toán. Tuy nhiên, các biểu thức khởi tạo của chúng được giảm xuống thành hằng số nếu chúng có thể tính toán được tại thời điểm biên dịch. Điều này có nghĩa là các thành viên không phải const enum không bao giờ được nội tuyến (hành vi này đã thay đổi trong TypeScript 1.5, xem "Thay đổi trong TypeScript" ở dưới cùng)

const so với không const

hăng sô

Một khai báo enum có thể có constsửa đổi. Nếu là một enum const, tất cả các tham chiếu đến các thành viên của nó được nội tuyến.

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

const enums không tạo ra một đối tượng tra cứu khi được biên dịch. Vì lý do này, đó là một lỗi khi tham chiếuFoo trong mã trên ngoại trừ là một phần của tham chiếu thành viên. Không có Foođối tượng nào sẽ hiện diện trong thời gian chạy.

không hằng số

Nếu một khai báo enum không có const bổ trợ, các tham chiếu đến các thành viên của nó chỉ được nội dòng nếu thành viên đó không được tính toán. Một enum không const, không khai báo sẽ tạo ra một đối tượng tra cứu.

khai báo (môi trường xung quanh) so với không khai báo

Một lời nói đầu quan trọng là declaretrong TypeScript có một ý nghĩa rất cụ thể: Đối tượng này tồn tại ở một nơi khác . Nó để mô tả các đối tượng hiện có . Sử dụngdeclare để xác định các đối tượng không thực sự tồn tại có thể gây ra hậu quả xấu; chúng ta sẽ khám phá những điều đó sau.

khai báo

A declare enum sẽ không phát ra một đối tượng tra cứu. Tham chiếu đến các thành viên của nó được nội tuyến nếu các thành viên đó được tính toán (xem ở trên về có tính toán và không được tính toán).

Điều quan trọng cần lưu ý là các hình thức tham chiếu đến một declare enum được cho phép, ví dụ như mã này là không một lỗi biên dịch nhưng sẽ thất bại khi chạy:

// 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

Lỗi này thuộc thể loại "Không nói dối trình biên dịch". Nếu bạn không có một đối tượng được đặt tên Footrong thời gian chạy, đừng viết declare enum Foo!

A declare const enumkhông khác với a const enum, ngoại trừ trong trường hợp --preserveConstEnums (xem bên dưới).

không khai báo

Một enum không khai báo tạo ra một đối tượng tra cứu nếu nó không phải const. Nội tuyến được mô tả ở trên.

--preserveConstEnums cờ

Cờ này có đúng một tác dụng: const enums không khai báo sẽ phát ra một đối tượng tra cứu. Nội tuyến không bị ảnh hưởng. Điều này rất hữu ích cho việc gỡ lỗi.


Lỗi thông thường

Sai lầm phổ biến nhất là sử dụng declare enumkhi một thông thường enumhoặc const enumsẽ thích hợp hơn. Một dạng phổ biến là:

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

Hãy nhớ nguyên tắc vàng: Không bao giờ có declarenhững thứ không thực sự tồn tại . Sử dụng const enumnếu bạn luôn muốn nội tuyến hoặc enumnếu bạn muốn đối tượng tra cứu.


Những thay đổi trong TypeScript

Giữa TypeScript 1.4 và 1.5, đã có một sự thay đổi trong hành vi (xem https://github.com/Microsoft/TypeScript/issues/2183 ) để làm cho tất cả các thành viên của enums không khai báo non-const được coi là được tính toán, ngay cả khi chúng được khởi tạo rõ ràng bằng một ký tự. Có thể nói, điều này làm cho hành vi nội tuyến trở nên dễ đoán hơn và tách biệt rõ ràng hơn khái niệm về const enumthông thường enum. Trước sự thay đổi này, các thành viên không được tính toán của các enums không phải const được sắp xếp tích cực hơn.


6
Một câu trả lời thực sự tuyệt vời. Nó đã giải tỏa rất nhiều thứ cho tôi, không chỉ là sự đố kỵ.
Clark

1
Tôi ước tôi có thể bỏ phiếu cho bạn nhiều hơn một lần ... không biết về sự thay đổi đột phá đó. Trong cách lập phiên bản ngữ nghĩa thích hợp, điều này có thể được coi là một cú
hích

Một so sánh rất hữu ích về các enumloại khác nhau, cảm ơn bạn!
Marius Schulz

@Ryan cái này rất hữu ích, cảm ơn! Bây giờ chúng ta chỉ cần Web Essentials 2015 để tạo ra các constkiểu enum thích hợp .
súng

19
Câu trả lời này dường như đi vào chi tiết giải thích một tình huống trong 1.4, và cuối cùng nó nói "nhưng 1.5 đã thay đổi tất cả và bây giờ nó đơn giản hơn nhiều." Giả sử tôi hiểu mọi thứ một cách chính xác, thì tổ chức này sẽ ngày càng trở nên không phù hợp khi câu trả lời này càng ngày càng cũ: Tôi thực sự khuyên bạn nên đặt tình huống hiện tại, đơn giản hơn lên trước và chỉ sau đó câu nói “nhưng nếu bạn đang sử dụng phiên bản 1.4 trở lên, mọi thứ phức tạp hơn một chút. "
KRyan

33

Có một vài điều đang diễn ra ở đây. Hãy đi từng trường hợp một.

enum

enum Cheese { Brie, Cheddar }

Đầu tiên, một enum cũ đơn giản. Khi được biên dịch sang JavaScript, nó sẽ phát ra một bảng tra cứu.

Bảng tra cứu có dạng như sau:

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

Sau đó, khi bạn có Cheese.Brietrong TypeScript, nó phát ra Cheese.Brietrong JavaScript đánh giá bằng 0. Cheese[0]phát ra Cheese[0]và thực sự đánh giá đến "Brie".

const enum

const enum Bread { Rye, Wheat }

Không có mã nào thực sự được phát ra cho việc này! Giá trị của nó được nội tuyến. Phần sau tự phát ra giá trị 0 trong JavaScript:

Bread.Rye
Bread['Rye']

const enumnội tuyến của s 'có thể hữu ích vì lý do hiệu suất.

Nhưng những gì về Bread[0]? Điều này sẽ xảy ra trong thời gian chạy và trình biên dịch của bạn sẽ bắt được nó. Không có bảng tra cứu và trình biên dịch không nội tuyến ở đây.

Lưu ý rằng trong trường hợp trên, cờ --preserveConstEnums sẽ khiến Bread phát ra một bảng tra cứu. Mặc dù vậy, các giá trị của nó vẫn sẽ được nội tuyến.

khai báo enum

Cũng như các cách sử dụng khác của declare, declarekhông tạo ra mã và mong rằng bạn đã xác định mã thực ở nơi khác. Điều này không tạo ra bảng tra cứu:

declare enum Wine { Red, Wine }

Wine.Redphát ra Wine.Redbằng JavaScript, nhưng sẽ không có bất kỳ bảng tra cứu Rượu nào để tham chiếu, vì vậy đó là lỗi trừ khi bạn đã xác định nó ở nơi khác.

khai báo const enum

Điều này không tạo ra bảng tra cứu:

declare const enum Fruit { Apple, Pear }

Nhưng nó không nội tuyến! Fruit.Applephát ra 0. Nhưng một lần nữa Fruit[0]sẽ xảy ra lỗi trong thời gian chạy vì nó không được nội tuyến và không có bảng tra cứu.

Tôi đã viết điều này trong sân chơi này . Tôi khuyên bạn nên chơi ở đó để hiểu TypeScript nào phát ra JavaScript nào.


1
Tôi khuyên bạn nên cập nhật câu trả lời này: Kể từ Typecript 3.3.3, Bread[0]tạo ra một lỗi trình biên dịch: “Thành viên const enum chỉ có thể được truy cập bằng cách sử dụng một chuỗi ký tự.”
chharvey

1
Hm ... có khác gì câu trả lời nói không? "Nhưng còn Bread [0] thì sao? Điều này sẽ xảy ra trong thời gian chạy và trình biên dịch của bạn sẽ bắt được nó. Không có bảng tra cứu và trình biên dịch không nội tuyến ở đây."
Kat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.