انواع مختلف انجمنها در تایپاسکریپت چگونه کار میکنند؟
تایپاسکریپت روشهای مختلفی برای تعریف یک 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 وجود دارد -- این با اینها چگونه تعامل دارد؟
پاسخ شماره ۱
چهار جنبه مختلف برای enums در TypeScript وجود دارد که باید از آنها آگاه باشید. ابتدا چند تعاریف:
«شیء جستجو»
اگر این شمارش را بنویسید:
enum Foo { X, Y }
تایپاسکریپت شیء زیر را تولید خواهد کرد:
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 تغییر کند.
انومها، چگونه کار میکنند؟
بیایید این را بر اساس هر جنبهای از یک enum تجزیه کنیم. متأسفانه، هر یک از این چهار بخش به اصطلاحاتی از سایر بخشها اشاره خواهد کرد، بنابراین احتمالاً باید کل این مطلب را بیش از یک بار بخوانید.
محاسبه شده در مقابل محاسبه نشده (ثابت)
اعضای Enum میتوانند یا محاسبهشده باشند یا نباشند. مشخصات، اعضای غیرمحاسبهشده را ثابت مینامد، اما من آنها را غیرمحاسبهشده مینامم تا از اشتباه با const جلوگیری شود.
یک عضو محاسبهشده از enum عضوی است که مقدار آن در زمان کامپایل مشخص نیست. البته ارجاعات به اعضای محاسبهشده نمیتوانند درونخطی شوند. بالعکس، یک عضو غیرمحاسبهشده از enum عضوی است که مقدار آن در زمان کامپایل مشخص است. ارجاعات به اعضای غیرمحاسبهشده همیشه درونخطی میشوند.
کدام اعضای enum محاسبه شده و کدام اعضای غیرمحاسبه شده هستند؟ ابتدا، همه اعضای یک const enum ثابت هستند (یعنی غیرمحاسبه شده)، همانطور که از نام آن پیداست. برای یک enum غیر ثابت، بستگی دارد که آیا شما به یک enum ambient (اعلام شده) نگاه میکنید یا یک enum غیر ambient.
یک عضو از یک declare enum (یعنی شمارش محیطی) ثابت است فقط در صورتی که دارای مقداردهنده اولیه باشد. در غیر این صورت، محاسبهشده است. توجه داشته باشید که در یک declare enum تنها مقداردهندههای عددی مجاز هستند. مثال:
declare enum Foo {
X, // Computed
Y = 2, // Non-computed
Z, // Computed! Not 3! Careful!
Q = 1 + 1 // Error
}
در نهایت، اعضای enum های غیر اعلام شده و غیر ثابت همیشه به عنوان مقادیر محاسبه شده در نظر گرفته میشوند. با این حال، عبارات مقداردهی اولیه آنها اگر در زمان کامپایل قابل محاسبه باشند، به ثابتها کاهش مییابند. این بدان معناست که اعضای enum غیر ثابت هرگز به صورت درونخطی استفاده نمیشوند (این رفتار در TypeScript 1.5 تغییر کرده است، به «تغییرات در TypeScript» در پایین مراجعه کنید).
ثابت در مقابل غیرثابت
ثابت
اعلام یک Enum میتواند شامل تغییردهنده const باشد. اگر یک Enum const باشد، تمام ارجاعات به اعضای آن درونخطی میشوند.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
enum های const هنگام کامپایل، یک شیء جستجو تولید نمیکنند. به همین دلیل، ارجاع به Foo در کد بالا به جز به عنوان بخشی از یک ارجاع عضوی خطا است. هیچ شیء Foo در زمان اجرا وجود نخواهد داشت.
غیرثابت
اگر یک اعلان enum دارای مودیفایر const نباشد، ارجاعات به اعضای آن تنها در صورتی به صورت inline درمیآیند که عضو محاسبه نشده باشد. یک enum غیرثابت و غیر declare یک شیء lookup تولید خواهد کرد.
اعلام (محیطی) در مقابل عدم اعلام
یک پیشگفتار مهم این است که 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 را ننویسید!
یک declare const enum با یک const enum تفاوتی ندارد، جز در مورد --preserveConstEnums (به زیر نگاه کنید).
غیر اعلامشده
یک enum غیر اعلان شده یک شیء جستجو ایجاد میکند اگر const نباشد. درونریزی در بالا توضیح داده شده است.
پرچم --preserveConstEnums
این فلگ دقیقاً یک اثر دارد: انومهای const غیر اعلام شده یک شیء جستجو تولید خواهند کرد. جایگذاری داخل خطی تحت تأثیر قرار نمیگیرد. این برای دیباگ کردن مفید است.
خطاهای رایج
متداولترین اشتباه استفاده از 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]);
}
قاعده طلایی را به یاد داشته باشید: هرگز چیزهایی که واقعاً وجود ندارند را اعلام نکنید. از const enum استفاده کنید اگر همیشه میخواهید درونخطی باشد، یا از enum استفاده کنید اگر میخواهید شیء جستجو داشته باشید.
تغییرات در تایپاسکریپت
بین نسخهcهای TypeScript 1.4 و 1.5، تغییری در رفتار ایجاد شد (به https://github.com/Microsoft/TypeScript/issues/2183 مراجعه کنید) بهcطوریcکه همه اعضای enum غیر اعلان و غیر ثابت محاسبهcشده در نظر گرفته میcشوند، حتی اگر به طور صریح با مقدار واقعی مقداردهی اولیه شوند. این به نوعی "راه حل میانی" تبدیل شد، که رفتار درونی را واضح تر و قابل پیش بینی تر کرد و مفهوم const enum را از enum معمولی جدا کرد. قبل از این تغییر، اعضای غیرمحاسبهcای enumcهای غیرمستقیم بهcطور تهاجمیcتر خطcبندی میcشدند.
پاسخ شماره ۲
چند چیز در اینجا در جریان است. بیایید مورد به مورد پیش برویم.
نم
enum Cheese { Brie, Cheddar }
اول، یک شمارنده قدیمی و ساده. وقتی به جاوااسکریپت کامپایل شود، یک جدول جستجو ایجاد خواهد کرد.
جدول جستجو به این شکل است:
var Cheese;
(function (Cheese) {
Cheese[Cheese["Brie"] = 0] = "Brie";
Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));
سپس وقتی شما Cheese.Brie را در TypeScript دارید، آن در JavaScript به Cheese.Brie تبدیل میشود که مقدار آن 0 است. Cheese[0] به Cheese[0] تبدیل میشود و در واقع مقدار آن "Brie" است.
ثابت شمارشی
const enum Bread { Rye, Wheat }
در واقع هیچ کدی برای این صادر نمیشود! مقادیر آن درونخطی هستند. موارد زیر مقدار ۰ را خودش در جاوااسکریپت صادر میکنند:
Bread.Rye
Bread['Rye']
درون خطی const enums ممکن است به دلایل عملکرد مفید باشد.
اما Bread[0] چه میشود؟ این در زمان اجرا خطا ایجاد میکند و کامپایلر شما باید آن را تشخیص دهد. جدول جستجویی وجود ندارد و کامپایلر در اینجا درونیابی نمیکند.
توجه داشته باشید که در مورد بالا، گزینه --preserveConstEnums باعث میشود Bread یک جدول جستجو منتشر کند. با این حال، مقادیر آن همچنان درونریز خواهند بود.
اعلان شمارش
همانند سایر استفادهها از declare، declare هیچ کدی ایجاد نمیکند و انتظار دارد که کد واقعی را در جای دیگری تعریف کرده باشید. این هیچ جدول جستجویی ایجاد نمیکند:
declare enum Wine { Red, Wine }
Wine.Red در جاوااسکریپت Wine.Red را صادر میکند، اما هیچ جدول جستجوی Wine برای ارجاع وجود نخواهد داشت، بنابراین این یک خطا است مگر اینکه آن را در جای دیگری تعریف کرده باشید.
اعلام کردن enum ثابت
این هیچ جدول جستجویی تولید نمیکند:
declare const enum Fruit { Apple, Pear }
اما این به صورت درونخطی عمل میکند! Fruit.Apple مقدار 0 را تولید میکند. اما دوباره Fruit[0] در زمان اجرا خطا میدهد زیرا درونخطی نشده و جدول جستجویی وجود ندارد.
من این را در این زمین بازی نوشتهام. من توصیه میکنم آنجا بازی کنید تا بفهمید TypeScript کدام JavaScript را تولید میکند.
