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.X
hoặ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ị 0
hoặc 1
và 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.ts
tệp là "môi trường xung quanh" (yêu cầu một công cụ declare
sử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 nó 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 const
enum 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ó const
sử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à declare
trong 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 Foo
trong thời gian chạy, đừng viết declare enum Foo
!
A declare const enum
khô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 enum
khi một thông thường enum
hoặc const enum
sẽ 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ó declare
những thứ không thực sự tồn tại . Sử dụng const enum
nếu bạn luôn muốn nội tuyến hoặc enum
nế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 enum
thô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.