با زنجیره اختیاری (?.) و ادغام تهی (??) در جاوا اسکریپت، کدی امنتر، تمیزتر و انعطافپذیرتر بنویسید. از خطاهای رایج زمان اجرا جلوگیری کرده و دادههای ناموجود را به زیبایی مدیریت کنید.
زنجیره اختیاری (Optional Chaining) و ادغام تهی (Nullish Coalescing) در جاوا اسکریپت: ساخت اپلیکیشنهای قوی و انعطافپذیر
در دنیای پویای توسعه وب، اپلیکیشنهای جاوا اسکریپت اغلب با منابع داده متنوعی، از APIهای REST گرفته تا ورودیهای کاربر و کتابخانههای شخص ثالث، تعامل دارند. این جریان مداوم اطلاعات به این معناست که ساختارهای داده همیشه قابل پیشبینی یا کامل نیستند. یکی از رایجترین دردسرهایی که توسعهدهندگان با آن مواجه میشوند، تلاش برای دسترسی به خصوصیات یک شیء است که ممکن است null یا undefined باشد، که منجر به خطای dreaded "TypeError: Cannot read properties of undefined (reading 'x')" میشود. این خطا میتواند اپلیکیشن شما را از کار بیندازد، تجربه کاربر را مختل کند و کد شما را با بررسیهای دفاعی متعدد، شلوغ و نامرتب نشان دهد.
خوشبختانه، جاوا اسکریپت مدرن دو عملگر قدرتمند – زنجیره اختیاری (?.) و ادغام تهی (??) – را معرفی کرده است که به طور خاص برای مقابله با این چالشها طراحی شدهاند. این ویژگیها که در ES2020 استاندارد شدهاند، به توسعهدهندگان در سراسر جهان اجازه میدهند تا هنگام کار با دادههای بالقوه ناموجود، کدی تمیزتر، انعطافپذیرتر و قویتر بنویسند. این راهنمای جامع به بررسی عمیق هر یک از این عملگرها میپردازد و عملکرد، مزایا، موارد استفاده پیشرفته و نحوه همکاری همافزای آنها برای ایجاد اپلیکیشنهای قابل پیشبینیتر و بدون خطا را بررسی میکند.
چه یک توسعهدهنده باتجربه جاوا اسکریپت باشید که راهحلهای پیچیده سازمانی میسازید، یا تازه سفر خود را آغاز کردهاید، تسلط بر زنجیره اختیاری و ادغام تهی به طور قابل توجهی مهارت کدنویسی شما را ارتقا میدهد و به شما کمک میکند تا اپلیکیشنهایی بسازید که به زیبایی با عدم قطعیتهای دادههای دنیای واقعی کنار بیایند.
مشکل: پیمایش دادههای بالقوه ناموجود
پیش از ظهور زنجیره اختیاری و ادغام تهی، توسعهدهندگان مجبور بودند برای دسترسی امن به خصوصیات تودرتو، به بررسیهای شرطی طولانی و تکراری تکیه کنند. بیایید یک سناریوی رایج را در نظر بگیریم: دسترسی به جزئیات آدرس یک کاربر، که ممکن است همیشه در شیء کاربری دریافت شده از یک API موجود نباشد.
رویکردهای سنتی و محدودیتهای آنها
۱. استفاده از عملگر AND منطقی (&&)
این یک تکنیک محبوب برای ایجاد اتصال کوتاه (short-circuiting) در دسترسی به خصوصیات بود. اگر هر بخشی از زنجیره مقدار falsy داشت، عبارت متوقف میشد و همان مقدار falsy را برمیگرداند.
const user = {
id: 'u123',
name: 'Alice Smith',
contact: {
email: 'alice@example.com',
phone: '123-456-7890'
}
// address is missing
};
// Attempt to get the street from user.address
const street = user && user.contact && user.contact.address && user.contact.address.street;
console.log(street); // undefined
const userWithAddress = {
id: 'u124',
name: 'Bob Johnson',
contact: {
email: 'bob@example.com'
},
address: {
street: '123 Main St',
city: 'Metropolis',
country: 'USA'
}
};
const city = userWithAddress && userWithAddress.address && userWithAddress.address.city;
console.log(city); // 'Metropolis'
// What if `user` itself is null or undefined?
const nullUser = null;
const streetFromNullUser = nullUser && nullUser.address && nullUser.address.street;
console.log(streetFromNullUser); // null (safe, but verbose)
اگرچه این رویکرد از خطا جلوگیری میکند، اما:
- طولانی است: هر سطح از تودرتویی نیاز به یک بررسی تکراری دارد.
- زائد است: نام متغیر چندین بار تکرار میشود.
- بالقوه گمراهکننده است: این روش میتواند هر مقدار falsy (مانند
0,'',false) را در صورت مواجهه در زنجیره برگرداند، که ممکن است رفتار مورد نظر هنگام بررسی خاص برایnullیاundefinedنباشد.
۲. دستورات If تودرتو
الگوی رایج دیگر شامل بررسی صریح وجود در هر سطح بود.
let country = 'Unknown';
if (userWithAddress) {
if (userWithAddress.address) {
if (userWithAddress.address.country) {
country = userWithAddress.address.country;
}
}
}
console.log(country); // 'USA'
// With the user object missing address:
const anotherUser = {
id: 'u125',
name: 'Charlie Brown'
};
let postcode = 'N/A';
if (anotherUser && anotherUser.address && anotherUser.address.postcode) {
postcode = anotherUser.address.postcode;
}
console.log(postcode); // 'N/A'
این رویکرد، گرچه صریح است، اما منجر به کدی با تورفتگی عمیق و سختخوان میشود که معمولاً هنگام اعمال بر روی دسترسی به خصوصیات به آن «جهنم کالبک» یا «هرم نابودی» میگویند. این روش با ساختارهای داده پیچیدهتر به خوبی مقیاسپذیر نیست.
این روشهای سنتی نیاز به یک راهحل زیباتر و مختصرتر برای پیمایش امن دادههای بالقوه ناموجود را برجسته میکنند. اینجاست که زنجیره اختیاری به عنوان یک تغییردهنده بازی برای توسعه جاوا اسکریپت مدرن وارد میشود.
معرفی زنجیره اختیاری (?.): راهنمای امن شما
زنجیره اختیاری یک افزودنی فوقالعاده به جاوا اسکریپت است که به شما امکان میدهد مقدار یک خصوصیت واقع در عمق یک زنجیره از اشیاء متصل به هم را بخوانید، بدون اینکه مجبور باشید به صراحت اعتبار هر مرجع در زنجیره را تأیید کنید. عملگر ?. شبیه به عملگر زنجیرهای . کار میکند، اما به جای ایجاد خطا اگر یک مرجع null یا undefined باشد، «اتصال کوتاه» برقرار کرده و undefined را برمیگرداند.
چگونه زنجیره اختیاری کار میکند
وقتی از عملگر زنجیره اختیاری (?.) در عبارتی مانند obj?.prop استفاده میکنید، موتور جاوا اسکریپت ابتدا obj را ارزیابی میکند. اگر obj نه null و نه undefined باشد، سپس به دسترسی به prop ادامه میدهد. اگر obj null یا undefined *باشد*، کل عبارت بلافاصله به undefined ارزیابی میشود و هیچ خطایی رخ نمیدهد.
این رفتار در چندین سطح از تودرتویی گسترش مییابد و برای خصوصیات، متدها و عناصر آرایه کار میکند.
سینتکس و مثالهای عملی
۱. دسترسی اختیاری به خصوصیت
این رایجترین مورد استفاده است که به شما امکان میدهد به طور امن به خصوصیات اشیاء تودرتو دسترسی پیدا کنید.
const userProfile = {
id: 'p001',
name: 'Maria Rodriguez',
location: {
city: 'Barcelona',
country: 'Spain'
},
preferences: null // preferences object is null
};
const companyData = {
name: 'Global Corp',
address: {
street: '456 Tech Ave',
city: 'Singapore',
postalCode: '123456'
},
contactInfo: undefined // contactInfo is undefined
};
// Accessing nested properties safely
console.log(userProfile?.location?.city); // 'Barcelona'
console.log(userProfile?.preferences?.theme); // undefined (because preferences is null)
console.log(companyData?.contactInfo?.email); // undefined (because contactInfo is undefined)
console.log(userProfile?.nonExistentProperty?.anotherOne); // undefined
// Without optional chaining, these would throw errors:
// console.log(userProfile.preferences.theme); // TypeError: Cannot read properties of null (reading 'theme')
// console.log(companyData.contactInfo.email); // TypeError: Cannot read properties of undefined (reading 'email')
۲. فراخوانی اختیاری متد
شما همچنین میتوانید هنگام فراخوانی متدی که ممکن است روی یک شیء وجود نداشته باشد، از زنجیره اختیاری استفاده کنید. اگر متد null یا undefined باشد، عبارت به undefined ارزیابی میشود و متد فراخوانی نمیشود.
const analyticsService = {
trackEvent: (name, data) => console.log(`Tracking event: ${name} with data:`, data)
};
const userService = {}; // No 'log' method here
analyticsService.trackEvent?.('user_login', { userId: 'u123' });
// Expected output: Tracking event: user_login with data: { userId: 'u123' }
userService.log?.('User updated', { id: 'u124' });
// Expected output: Nothing happens, no error thrown. The expression returns undefined.
این قابلیت هنگام کار با کالبکهای اختیاری، پلاگینها یا پرچمهای ویژگی (feature flags) که یک تابع ممکن است به صورت شرطی وجود داشته باشد، فوقالعاده مفید است.
۳. دسترسی اختیاری با نمادگذاری آرایه/براکت
زنجیره اختیاری همچنین با نمادگذاری براکت برای دسترسی به عناصر یک آرایه یا خصوصیات با کاراکترهای خاص کار میکند.
const userActivities = {
events: ['login', 'logout', 'view_profile'],
purchases: []
};
const globalSettings = {
'app-name': 'My App',
'version-info': {
'latest-build': '1.0.0'
}
};
console.log(userActivities?.events?.[0]); // 'login'
console.log(userActivities?.purchases?.[0]); // undefined (empty array, so element at index 0 is undefined)
console.log(userActivities?.preferences?.[0]); // undefined (preferences is not defined)
// Accessing properties with hyphens using bracket notation
console.log(globalSettings?.['app-name']); // 'My App'
console.log(globalSettings?.['version-info']?.['latest-build']); // '1.0.0'
console.log(globalSettings?.['config']?.['env']); // undefined
مزایای کلیدی زنجیره اختیاری
-
خوانایی و ایجاز: این عملگر به طور چشمگیری میزان کد قالبی مورد نیاز برای بررسیهای دفاعی را کاهش میدهد. کد شما بسیار تمیزتر و در یک نگاه قابل فهمتر میشود.
// Before const regionCode = (user && user.address && user.address.country && user.address.country.region) ? user.address.country.region : 'N/A'; // After const regionCode = user?.address?.country?.region ?? 'N/A'; // (combining with nullish coalescing for default value) -
جلوگیری از خطا: خطاهای زمان اجرای
TypeErrorناشی از تلاش برای دسترسی به خصوصیاتnullیاundefinedرا از بین میبرد. این منجر به اپلیکیشنهای پایدارتر میشود. - بهبود تجربه توسعهدهنده: توسعهدهندگان میتوانند بیشتر بر روی منطق کسبوکار تمرکز کنند تا برنامهنویسی دفاعی، که منجر به چرخههای توسعه سریعتر و باگهای کمتر میشود.
- مدیریت زیبای دادهها: این عملگر به اپلیکیشنها اجازه میدهد تا سناریوهایی را که دادهها ممکن است به صورت جزئی در دسترس باشند یا ساختاری متفاوت از انتظار داشته باشند، به زیبایی مدیریت کنند، که هنگام کار با APIهای خارجی یا محتوای تولید شده توسط کاربر از منابع مختلف بینالمللی رایج است. به عنوان مثال، جزئیات تماس یک کاربر ممکن است در برخی مناطق اختیاری اما در برخی دیگر اجباری باشد.
چه زمانی از زنجیره اختیاری استفاده کنیم و چه زمانی نه
در حالی که زنجیره اختیاری فوقالعاده مفید است، درک کاربرد مناسب آن بسیار مهم است:
از زنجیره اختیاری استفاده کنید وقتی:
-
یک خصوصیت یا متد واقعاً اختیاری است: این بدان معناست که قابل قبول است که مرجع میانی
nullیاundefinedباشد، و اپلیکیشن شما میتواند بدون آن ادامه دهد، احتمالاً با استفاده از یک مقدار پیشفرض.const dashboardConfig = { theme: 'dark', modules: [ { name: 'Analytics', enabled: true }, { name: 'Reports', enabled: false } ] }; // If 'notifications' module is optional const notificationsEnabled = dashboardConfig.modules.find(m => m.name === 'Notifications')?.enabled; console.log(notificationsEnabled); // undefined if not found - با پاسخهای API که ممکن است ساختارهای متناقضی داشته باشند سر و کار دارید: دادههای دریافتی از endpointهای مختلف یا نسخههای متفاوت یک API گاهی اوقات ممکن است برخی فیلدها را حذف کنند. زنجیره اختیاری به شما کمک میکند تا چنین دادههایی را به صورت امن مصرف کنید.
-
به خصوصیات اشیاء تولید شده به صورت پویا یا ارائه شده توسط کاربر دسترسی دارید: وقتی نمیتوانید شکل یک شیء را تضمین کنید،
?.یک شبکه ایمنی فراهم میکند.
از زنجیره اختیاری اجتناب کنید وقتی:
-
یک خصوصیت یا متد حیاتی است و *باید* وجود داشته باشد: اگر عدم وجود یک خصوصیت نشاندهنده یک باگ جدی یا یک وضعیت نامعتبر است، باید اجازه دهید خطای
TypeErrorرخ دهد تا بتوانید مشکل اساسی را شناسایی و رفع کنید. استفاده از?.در اینجا مشکل را پنهان میکند.// If 'userId' is absolutely critical for every user object const user = { name: 'Jane' }; // Missing 'id' // A TypeError here would indicate a serious data integrity issue // console.log(user?.id); // Returns undefined, potentially masking an error // Prefer to let it error or explicitly check: if (!user.id) { throw new Error('User ID is missing and required!'); } -
خوانایی به دلیل زنجیرهسازی بیش از حد آسیب میبیند: اگرچه مختصر است، یک زنجیره اختیاری بسیار طولانی (مانند
obj?.prop1?.prop2?.prop3?.prop4?.prop5) میتواند خواندن را دشوار کند. گاهی اوقات، شکستن آن یا بازسازی دادههای شما ممکن است بهتر باشد. -
باید بین
null/undefinedو دیگر مقادیر falsy (0,'',false) تمایز قائل شوید: زنجیره اختیاری فقط برایnullیاundefinedبررسی میکند. اگر نیاز به مدیریت متفاوت سایر مقادیر falsy دارید، ممکن است به یک بررسی صریحتر نیاز داشته باشید یا آن را با ادغام تهی ترکیب کنید، که در ادامه به آن خواهیم پرداخت.
درک ادغام تهی (??): مقادیر پیشفرض دقیق
در حالی که زنجیره اختیاری به شما کمک میکند به طور امن به خصوصیاتی که *ممکن است* وجود نداشته باشند دسترسی پیدا کنید، ادغام تهی (??) به شما کمک میکند تا به طور خاص زمانی که یک مقدار null یا undefined است، یک مقدار پیشفرض ارائه دهید. این عملگر اغلب در ترکیب با زنجیره اختیاری استفاده میشود، اما رفتار متمایزی دارد و مشکلی متفاوت از عملگر سنتی OR منطقی (||) را حل میکند.
چگونه ادغام تهی کار میکند
عملگر ادغام تهی (??) عملوند سمت راست خود را زمانی برمیگرداند که عملوند سمت چپ آن null یا undefined باشد، و در غیر این صورت عملوند سمت چپ خود را برمیگرداند. این یک تمایز حیاتی از || است زیرا مقادیر falsy دیگر (مانند 0, '', false) را به عنوان nullish در نظر نمیگیرد.
تفاوت با OR منطقی (||)
این شاید مهمترین مفهومی باشد که باید هنگام درک ?? به آن مسلط شوید.
-
OR منطقی (
||): عملوند سمت راست را برمیگرداند اگر عملوند سمت چپ هر مقدار falsy باشد (false,0,'',null,undefined,NaN). -
ادغام تهی (
??): عملوند سمت راست را فقط در صورتی برمیگرداند که عملوند سمت چپ به طور خاصnullیاundefinedباشد.
بیایید به مثالهایی برای روشنتر شدن این تفاوت نگاه کنیم:
// Example 1: With 'null' or 'undefined'
const nullValue = null;
const undefinedValue = undefined;
const defaultValue = 'Default Value';
console.log(nullValue || defaultValue); // 'Default Value'
console.log(nullValue ?? defaultValue); // 'Default Value'
console.log(undefinedValue || defaultValue); // 'Default Value'
console.log(undefinedValue ?? defaultValue); // 'Default Value'
// --- Behavior diverges here ---
// Example 2: With 'false'
const falseValue = false;
console.log(falseValue || defaultValue); // 'Default Value' (|| treats false as falsy)
console.log(falseValue ?? defaultValue); // false (?? treats false as a valid value)
// Example 3: With '0'
const zeroValue = 0;
console.log(zeroValue || defaultValue); // 'Default Value' (|| treats 0 as falsy)
console.log(zeroValue ?? defaultValue); // 0 (?? treats 0 as a valid value)
// Example 4: With empty string ''
const emptyString = '';
console.log(emptyString || defaultValue); // 'Default Value' (|| treats '' as falsy)
console.log(emptyString ?? defaultValue); // '' (?? treats '' as a valid value)
// Example 5: With NaN
const nanValue = NaN;
console.log(nanValue || defaultValue); // 'Default Value' (|| treats NaN as falsy)
console.log(nanValue ?? defaultValue); // NaN (?? treats NaN as a valid value)
نکته کلیدی این است که ?? کنترل بسیار دقیقتری بر روی مقادیر پیشفرض فراهم میکند. اگر 0, false یا یک رشته خالی '' مقادیر معتبر و معناداری در منطق اپلیکیشن شما هستند، پس ?? عملگری است که باید برای تنظیم مقادیر پیشفرض استفاده کنید، زیرا || به اشتباه آنها را جایگزین میکند.
سینتکس و مثالهای عملی
۱. تنظیم مقادیر پیشفرض پیکربندی
این یک مورد استفاده عالی برای ادغام تهی است، که تضمین میکند تنظیمات صریح معتبر (حتی اگر falsy باشند) حفظ میشوند، در حالی که تنظیمات واقعاً ناموجود یک مقدار پیشفرض دریافت میکنند.
const userSettings = {
theme: 'light',
fontSize: 14,
enableNotifications: false, // User explicitly set to false
animationSpeed: null // animationSpeed explicitly set to null (perhaps to inherit default)
};
const defaultSettings = {
theme: 'dark',
fontSize: 16,
enableNotifications: true,
animationSpeed: 300
};
const currentTheme = userSettings.theme ?? defaultSettings.theme;
console.log(`Current Theme: ${currentTheme}`); // 'light'
const currentFontSize = userSettings.fontSize ?? defaultSettings.fontSize;
console.log(`Current Font Size: ${currentFontSize}`); // 14 (not 16, because 0 is a valid number)
const notificationsEnabled = userSettings.enableNotifications ?? defaultSettings.enableNotifications;
console.log(`Notifications Enabled: ${notificationsEnabled}`); // false (not true, because false is a valid boolean)
const animationDuration = userSettings.animationSpeed ?? defaultSettings.animationSpeed;
console.log(`Animation Duration: ${animationDuration}`); // 300 (because animationSpeed was null)
const language = userSettings.language ?? 'en-US'; // language is not defined
console.log(`Selected Language: ${language}`); // 'en-US'
۲. مدیریت پارامترهای اختیاری API یا ورودی کاربر
هنگام ساخت درخواستهای API یا پردازش فرمهای ارسالی کاربر، برخی فیلدها ممکن است اختیاری باشند. ?? به شما کمک میکند مقادیر پیشفرض معقولی را بدون جایگزینی مقادیر صفر یا false معتبر، اختصاص دهید.
function searchProducts(query, options) {
const resultsPerPage = options?.limit ?? 20; // Default to 20 if limit is null/undefined
const minPrice = options?.minPrice ?? 0; // Default to 0, allowing actual 0 as a valid min price
const sortBy = options?.sortBy ?? 'relevance';
console.log(`Searching for: '${query}'`);
console.log(` Results per page: ${resultsPerPage}`);
console.log(` Minimum price: ${minPrice}`);
console.log(` Sort by: ${sortBy}`);
}
searchProducts('laptops', { limit: 10, minPrice: 500 });
// Expected:
// Searching for: 'laptops'
// Results per page: 10
// Minimum price: 500
// Sort by: relevance
searchProducts('keyboards', { minPrice: 0, sortBy: null }); // minPrice is 0, sortBy is null
// Expected:
// Searching for: 'keyboards'
// Results per page: 20
// Minimum price: 0
// Sort by: relevance (because sortBy was null)
searchProducts('monitors', {}); // No options provided
// Expected:
// Searching for: 'monitors'
// Results per page: 20
// Minimum price: 0
// Sort by: relevance
مزایای کلیدی ادغام تهی
-
دقت در مقادیر پیشفرض: تضمین میکند که فقط مقادیر واقعاً ناموجود (
nullیاundefined) با یک مقدار پیشفرض جایگزین میشوند، و مقادیر falsy معتبر مانند0,'', یاfalseرا حفظ میکند. -
قصد واضحتر: به صراحت بیان میکند که شما فقط میخواهید یک جایگزین برای
nullیاundefinedارائه دهید، و منطق کد شما را شفافتر میکند. -
استحکام: از عوارض جانبی ناخواسته جلوگیری میکند که در آن یک مقدار معتبر
0یاfalseممکن بود هنگام استفاده از||با یک مقدار پیشفرض جایگزین شود. -
کاربرد جهانی: این دقت برای اپلیکیشنهایی که با انواع دادههای متنوع سروکار دارند، مانند اپلیکیشنهای مالی که در آن
0یک مقدار مهم است، یا تنظیمات بینالمللیسازی که یک رشته خالی ممکن است یک انتخاب عمدی را نشان دهد، حیاتی است.
زوج قدرتمند: زنجیره اختیاری و ادغام تهی با هم
در حالی که زنجیره اختیاری و ادغام تهی به تنهایی قدرتمند هستند، زمانی که در ترکیب با هم استفاده میشوند واقعاً میدرخشند. این همافزایی امکان دسترسی به دادهها را به شیوهای فوقالعاده قوی و مختصر با مدیریت دقیق مقادیر پیشفرض فراهم میکند. شما میتوانید به طور امن در ساختارهای شیء بالقوه ناموجود جستجو کنید و سپس، اگر مقدار نهایی null یا undefined بود، بلافاصله یک جایگزین معنادار ارائه دهید.
مثالهای همافزا
۱. دسترسی به خصوصیات تودرتو با یک جایگزین پیشفرض
این رایجترین و تأثیرگذارترین مورد استفاده ترکیبی است.
const userData = {
id: 'user-007',
name: 'James Bond',
contactDetails: {
email: 'james.bond@mi6.gov.uk',
phone: '007-007-0070'
},
// preferences is missing
address: {
street: 'Whitehall St',
city: 'London'
// postcode is missing
}
};
const clientData = {
id: 'client-101',
name: 'Global Ventures Inc.',
location: {
city: 'New York'
}
};
const guestData = {
id: 'guest-999'
};
// Safely get user's preferred language, defaulting to 'en-GB'
const userLang = userData?.preferences?.language ?? 'en-GB';
console.log(`User Language: ${userLang}`); // 'en-GB'
// Get client's country, defaulting to 'Unknown'
const clientCountry = clientData?.location?.country ?? 'Unknown';
console.log(`Client Country: ${clientCountry}`); // 'Unknown'
// Get a guest's display name, defaulting to 'Guest'
const guestDisplayName = guestData?.displayName ?? 'Guest';
console.log(`Guest Display Name: ${guestDisplayName}`); // 'Guest'
// Get user's postcode, defaulting to 'N/A'
const userPostcode = userData?.address?.postcode ?? 'N/A';
console.log(`User Postcode: ${userPostcode}`); // 'N/A'
// What if an explicitly empty string is valid?
const profileWithEmptyBio = {
username: 'coder',
info: { bio: '' }
};
const profileWithNullBio = {
username: 'developer',
info: { bio: null }
};
const bio1 = profileWithEmptyBio?.info?.bio ?? 'No bio provided';
console.log(`Bio 1: '${bio1}'`); // Bio 1: '' (empty string is preserved)
const bio2 = profileWithNullBio?.info?.bio ?? 'No bio provided';
console.log(`Bio 2: '${bio2}'`); // Bio 2: 'No bio provided' (null is replaced)
۲. فراخوانی شرطی متدها با یک اقدام جایگزین
شما میتوانید از این ترکیب برای اجرای یک متد در صورت وجود استفاده کنید، در غیر این صورت یک اقدام پیشفرض انجام دهید یا یک پیام ثبت کنید.
const logger = {
log: (message) => console.log(`[INFO] ${message}`)
};
const analytics = {}; // No 'track' method
const systemEvent = 'application_start';
// Try to track event, otherwise just log it
analytics.track?.(systemEvent, { origin: 'bootstrap' }) ?? logger.log(`Fallback: Could not track event '${systemEvent}'`);
// Expected: [INFO] Fallback: Could not track event 'application_start'
const anotherLogger = {
warn: (msg) => console.warn(`[WARN] ${msg}`),
log: (msg) => console.log(`[LOG] ${msg}`)
};
anotherLogger.track?.('test') ?? anotherLogger.warn('Track method not available.');
// Expected: [WARN] Track method not available.
۳. مدیریت دادههای بینالمللیسازی (i18n)
در اپلیکیشنهای جهانی، ساختارهای داده i18n میتوانند پیچیده باشند و برخی ترجمهها ممکن است برای زبانهای خاصی وجود نداشته باشند. این ترکیب یک مکانیزم جایگزین قوی را تضمین میکند.
const translations = {
'en-US': {
greeting: 'Hello',
messages: {
welcome: 'Welcome!',
error: 'An error occurred.'
}
},
'es-ES': {
greeting: 'Hola',
messages: {
welcome: '¡Bienvenido!',
loading: 'Cargando...'
}
}
};
function getTranslation(locale, keyPath, defaultValue) {
// Split keyPath into an array of properties
const keys = keyPath.split('.');
// Dynamically access nested properties using optional chaining
let result = translations[locale];
for (const key of keys) {
result = result?.[key];
}
// Provide a default if the translation is null or undefined
return result ?? defaultValue;
}
console.log(getTranslation('en-US', 'messages.welcome', 'Fallback Welcome')); // 'Welcome!'
console.log(getTranslation('es-ES', 'messages.welcome', 'Fallback Welcome')); // '¡Bienvenido!'
console.log(getTranslation('es-ES', 'messages.error', 'Fallback Error')); // 'Fallback Error' (error is missing in es-ES)
console.log(getTranslation('fr-FR', 'greeting', 'Bonjour')); // 'Bonjour' (fr-FR locale is missing entirely)
این مثال به زیبایی نشان میدهد که چگونه ?. امکان پیمایش امن در میان اشیاء زبان بالقوه ناموجود و کلیدهای پیام تودرتو را فراهم میکند، در حالی که ?? تضمین میکند که اگر یک ترجمه خاص وجود نداشت، به جای undefined، یک مقدار پیشفرض معقول ارائه شود.
موارد استفاده پیشرفته و ملاحظات
۱. رفتار اتصال کوتاه (Short-Circuiting)
مهم است به یاد داشته باشید که زنجیره اختیاری اتصال کوتاه ایجاد میکند. این بدان معناست که اگر یک عملوند در زنجیره به null یا undefined ارزیابی شود، بقیه عبارت ارزیابی نمیشود. این میتواند برای عملکرد و جلوگیری از عوارض جانبی مفید باشد.
let count = 0;
const user = {
name: 'Anna',
getAddress: () => {
count++;
console.log('Fetching address...');
return { city: 'Paris' };
}
};
const admin = null;
// user exists, getAddress is called
console.log(user?.getAddress()?.city); // Output: Fetching address..., then 'Paris'
console.log(count); // 1
// admin is null, getAddress is NOT called
console.log(admin?.getAddress()?.city); // Output: undefined
console.log(count); // Still 1 (getAddress was not executed)
۲. زنجیره اختیاری با واسازی (De-structuring) (کاربرد با احتیاط)
در حالی که نمیتوانید مستقیماً از زنجیره اختیاری در یک *انتساب* واسازی مانند const { user?.profile } = data; استفاده کنید، میتوانید از آن هنگام تعریف متغیرها از یک شیء و سپس ارائه مقادیر جایگزین، یا با واسازی پس از دسترسی امن به خصوصیت استفاده کنید.
const apiResponse = {
success: true,
payload: {
data: {
user: {
id: 'u456',
name: 'David',
email: 'david@example.com'
}
}
}
};
const emptyResponse = {
success: false
};
// Extracting deeply nested data with a default
const userId = apiResponse?.payload?.data?.user?.id ?? 'guest';
const userName = apiResponse?.payload?.data?.user?.name ?? 'Anonymous';
console.log(`User ID: ${userId}, Name: ${userName}`); // User ID: u456, Name: David
const guestId = emptyResponse?.payload?.data?.user?.id ?? 'guest';
const guestName = emptyResponse?.payload?.data?.user?.name ?? 'Anonymous';
console.log(`Guest ID: ${guestId}, Name: ${guestName}`); // Guest ID: guest, Name: Anonymous
// A common pattern is to safely access an object first, then destructure if it exists:
const { user: userDataFromResponse } = apiResponse.payload.data;
const { id = 'default-id', name = 'Default Name' } = userDataFromResponse ?? {};
console.log(`Destructured ID: ${id}, Name: ${name}`); // Destructured ID: u456, Name: David
// For an empty response:
const { user: userDataFromEmptyResponse } = emptyResponse.payload?.data ?? {}; // Use optional chaining for payload.data, then ?? {} for user
const { id: emptyId = 'default-id', name: emptyName = 'Default Name' } = userDataFromEmptyResponse ?? {};
console.log(`Destructured Empty ID: ${emptyId}, Name: ${emptyName}`); // Destructured Empty ID: default-id, Name: Default Name
۳. اولویت و گروهبندی عملگرها
زنجیره اختیاری (?.) اولویت بالاتری نسبت به ادغام تهی (??) دارد. این بدان معناست که a?.b ?? c به صورت (a?.b) ?? c تفسیر میشود، که معمولاً رفتار مورد نظر است. شما معمولاً برای این ترکیب نیازی به پرانتز اضافی نخواهید داشت.
const config = {
value: null
};
// Correctly evaluates to (config?.value) ?? 'default'
const result = config?.value ?? 'default';
console.log(result); // 'default'
// If the value was 0:
const configWithZero = {
value: 0
};
const resultZero = configWithZero?.value ?? 'default';
console.log(resultZero); // 0 (as 0 is not nullish)
۴. ادغام با بررسی نوع (مانند TypeScript)
برای توسعهدهندگانی که از TypeScript استفاده میکنند، عملگرهای زنجیره اختیاری و ادغام تهی به طور کامل پشتیبانی میشوند و ایمنی نوع را افزایش میدهند. TypeScript میتواند از این عملگرها برای استنتاج صحیح انواع استفاده کند، نیاز به بررسیهای صریح null را در سناریوهای خاص کاهش دهد و سیستم نوع را حتی قدرتمندتر کند.
// Example in TypeScript (conceptual, not runnable JS)
interface User {
id: string;
name: string;
email?: string; // email is optional
address?: {
street: string;
city: string;
zipCode?: string; // zipCode is optional
};
}
function getUserEmail(user: User): string {
// TypeScript understands user.email could be undefined, and handles it with ??
return user.email ?? 'No email provided';
}
function getUserZipCode(user: User): string {
// TypeScript understands address and zipCode are optional
return user.address?.zipCode ?? 'N/A';
}
const user1: User = { id: '1', name: 'John Doe', email: 'john@example.com', address: { street: 'Main', city: 'Town' } };
const user2: User = { id: '2', name: 'Jane Doe' }; // No email or address
console.log(getUserEmail(user1)); // 'john@example.com'
console.log(getUserEmail(user2)); // 'No email provided'
console.log(getUserZipCode(user1)); // 'N/A' (zipCode is missing)
console.log(getUserZipCode(user2)); // 'N/A' (address is missing)
این ادغام، توسعه را سادهتر میکند، زیرا کامپایلر به شما کمک میکند تا اطمینان حاصل کنید که تمام مسیرهای اختیاری و nullish به درستی مدیریت میشوند، و این امر خطاهای زمان اجرا را بیشتر کاهش میدهد.
بهترین شیوهها و دیدگاه جهانی
پذیرش مؤثر زنجیره اختیاری و ادغام تهی فراتر از درک سینتکس آنهاست؛ این نیازمند یک رویکرد استراتژیک برای مدیریت داده و طراحی کد است، به ویژه برای اپلیکیشنهایی که به مخاطبان جهانی خدمت میکنند.
۱. دادههای خود را بشناسید
همیشه سعی کنید ساختارهای بالقوه دادههای خود را، به ویژه از منابع خارجی، درک کنید. در حالی که ?. و ?? ایمنی را ارائه میدهند، جایگزین نیاز به قراردادهای دادهای واضح یا مستندات API نمیشوند. از آنها زمانی استفاده کنید که انتظار میرود یک فیلد *اختیاری* باشد یا ممکن است وجود نداشته باشد، نه به عنوان یک راهحل کلی برای اسکیمای دادههای ناشناخته.
۲. تعادل بین ایجاز و خوانایی
در حالی که این عملگرها کد را کوتاهتر میکنند، زنجیرههای بیش از حد طولانی هنوز هم میتوانند خواندن را دشوار کنند. در صورت بهبود وضوح، شکستن مسیرهای دسترسی بسیار عمیق یا ایجاد متغیرهای میانی را در نظر بگیرید.
// Potentially less readable:
const userCity = clientRequest?.customer?.billing?.primaryAddress?.location?.city?.toUpperCase() ?? 'UNKNOWN';
// More readable breakdown:
const primaryAddress = clientRequest?.customer?.billing?.primaryAddress;
const userCity = primaryAddress?.location?.city?.toUpperCase() ?? 'UNKNOWN';
۳. تمایز بین 'ناموجود' و 'صریحاً خالی/صفر'
اینجاست که ?? واقعاً میدرخشد. برای فرمهای بینالمللی یا ورود داده، یک کاربر ممکن است به صراحت '0' را برای یک کمیت، 'false' را برای یک تنظیم بولین، یا یک رشته خالی '' را برای یک نظر اختیاری وارد کند. اینها ورودیهای معتبری هستند و نباید با یک مقدار پیشفرض جایگزین شوند. ?? این دقت را تضمین میکند، برخلاف || که آنها را به عنوان محرکی برای یک مقدار پیشفرض در نظر میگیرد.
۴. مدیریت خطا: هنوز ضروری است
زنجیره اختیاری از خطای TypeError برای دسترسی به null/undefined جلوگیری میکند، اما از انواع دیگر خطاها (مانند خطاهای شبکه، آرگومانهای نامعتبر تابع، خطاهای منطقی) جلوگیری نمیکند. یک اپلیکیشن قوی هنوز به استراتژیهای جامع مدیریت خطا مانند بلوکهای try...catch برای سایر مشکلات بالقوه نیاز دارد.
۵. پشتیبانی مرورگر/محیط را در نظر بگیرید
زنجیره اختیاری و ادغام تهی ویژگیهای مدرن جاوا اسکریپت (ES2020) هستند. در حالی که در مرورگرهای معاصر و نسخههای Node.js به طور گسترده پشتیبانی میشوند، اگر محیطهای قدیمیتری را هدف قرار دادهاید، ممکن است نیاز به تبدیل (transpile) کد خود با استفاده از ابزارهایی مانند Babel داشته باشید. همیشه آمار مرورگر مخاطبان هدف خود را بررسی کنید تا از سازگاری اطمینان حاصل کنید یا برای تبدیل کد برنامهریزی کنید.
۶. دیدگاه جهانی در مورد مقادیر پیشفرض
هنگام ارائه مقادیر پیشفرض، مخاطبان جهانی خود را در نظر بگیرید. برای مثال:
- تاریخ و زمان: پیشفرضگذاری به یک منطقه زمانی یا فرمت خاص باید با توجه به مکان کاربر باشد.
- ارزها: یک ارز پیشفرض (مانند USD) ممکن است برای همه کاربران مناسب نباشد.
- زبان: همیشه یک زبان جایگزین معقول (مانند انگلیسی) در صورت عدم وجود ترجمه یک زبان خاص ارائه دهید.
- واحدهای اندازهگیری: پیشفرضگذاری به 'متریک' یا 'امپریال' باید با توجه به زمینه باشد.
این عملگرها پیادهسازی چنین پیشفرضهای آگاه از زمینه را به شیوهای زیبا آسانتر میکنند.
نتیجهگیری
عملگرهای زنجیره اختیاری (?.) و ادغام تهی (??) جاوا اسکریپت ابزارهای ضروری برای هر توسعهدهنده مدرن هستند. آنها راهحلهای زیبا، مختصر و قوی برای مشکلات رایج مرتبط با مدیریت دادههای بالقوه ناموجود یا تعریفنشده در ساختارهای پیچیده اشیاء ارائه میدهند.
با بهرهگیری از زنجیره اختیاری، میتوانید به طور امن در مسیرهای عمیق خصوصیات پیمایش کنید و متدها را بدون ترس از خطاهای TypeErrors که برنامه را از کار میاندازند، فراخوانی کنید. با ادغام ادغام تهی، کنترل دقیقی بر روی مقادیر پیشفرض به دست میآورید و تضمین میکنید که فقط مقادیر واقعاً null یا undefined جایگزین میشوند، در حالی که مقادیر falsy معتبر مانند 0 یا false حفظ میشوند.
این «زوج قدرتمند» با هم به طور چشمگیری خوانایی کد را بهبود میبخشند، کد قالبی را کاهش میدهند و منجر به اپلیکیشنهای انعطافپذیرتری میشوند که به زیبایی با ماهیت غیرقابل پیشبینی دادههای دنیای واقعی در محیطهای متنوع جهانی کنار میآیند. پذیرش این ویژگیها گامی روشن به سوی نوشتن کدی تمیزتر، قابل نگهداریتر و بسیار حرفهای در جاوا اسکریپت است. از امروز شروع به ادغام آنها در پروژههای خود کنید و تفاوتی را که در ساخت اپلیکیشنهای واقعاً قوی برای کاربران در سراسر جهان ایجاد میکنند، تجربه کنید!