بررسی Optional Chaining (.?) در جاوااسکریپت

اگر به تازگی شروع به خواندن آموزش و یادگیری جاوا اسکریپت کرده اید ، شاید این مشکل را هنوز لمس نکرده اید ، اما این یک مشکل کاملاً رایج است.

برای مثال, بیاید یک شی(object) برای ذخیره کردن اطلاعات کاربرانمان در نظر بگیریم. اکثر کاربران ما ویژگی(property) آدرس را در user.address و خیابان را در user.address.street را دارند ولی بعضی از آنان این اطلاعات را ارائه نکرده اند.

در همچین مثالی تلاش ما برای دریافت مقدار user.address.street, برای کاربری که آدرس ندارد با خطا مواجه شود :

let user = {}; // یک کاربر بدون ویژگی &quotaddress&quot
alert(user.address.street); // خطا

این یک خروجی قابل حدس است٬ جاوااسکریپت اینگونه کار می‌کند٬ تا زمانی که user.address برابر با undefined است تلاش برای گرفتن آن با خطا مواجه می‌شود. ولی در بسیاری از موارد عملی ، ما ترجیح می دهیم به جای خطا ، ‍undefined را دریافت کنیم (به معنای "بدون خیابان").

یا مثالی دیگر در توسعه وب٬ ما می‌خواهیم اطلاعاتی در مورد اِلمانی در صفحه را بگیریم.این اِلمان توسط این عبارت document.querySelector('.elem') گرفته می‌شود که ممکن است گاهی وجود نداشته باشد:

document.querySelector('.elem') //  خواهد شد اگر المنت وجود نداشته باشد null
let html = document.querySelector('.elem');  // اگر باشد خطا خواهد داد NULL

یکبار دیگر٬ اگر اِلمان وجود نداشته باشد ما با مقدار NULL نمی‌توانیم به دسترسی داشته باشیم. و در بعضی موارد وقتی که نبود اِلمان طبیعی است ، ما می‌خواهیم از خطا جلوگیری کنیم و فقط "html = null" را قبول کنیم.

چگونه می‌توانیم از این استفاده کنیم ؟

راه‌حل روشن این است که مقدار آن را با if یا عمگر شرطی ? بررسی کنیم قبل از اینکه به ویژگی (property) آن دسترسی پیدا کنیم.

let user = {}; 
alert(user.address ? user.address.street : undefined);

الان بدون خطا کار می‌کند... ولی اصلا زیبا نیست. همانطور که میبینید مقدار"user.address" دوبار در کد تکرار شده است. برای دسترسی به ویژگی‌های(property) با تو در تویی زیاد نیاز به تکرار بیشتری لازم است و این مشکل ایجاد میکند.
برای مثال بیاید مقدار عبارت user.address.street.nameرا بگیریم.
در این صورت ما باید هم user.address و user.address.street را بررسی کنیم :

let user = {}; // کاربر بدون آدرس
alert(user.address ? user.address.street ? user.address.street.name : null : null);

این افتضاح است یک نفر ممکن حتی با درک این کد مشکل داشته باشد.
قبل از اینکه زنجیره اختیاری به جاوااسکریپت اضافه شود مردم از عملگر && برای بعضی مواقع استفاده میکردند:

نگران نباشید :) ٬ راه های بهتری هم هست میتوانیم از عملگر && استفاده کنیم.

let user = {}; // کاربر بدون آدرس
alert( user.address && user.address.street && user.address.street.name ); // undefined (بدون خطا)

اند(AND) کردن کل مسیر رسیدن به ویژگی ، وجود همه ویژگی ها(property) را تضمین می کند(اگر ارزیابی متوقف نشود) ، اما نوشتن آن دست و پا گیر است.

همانطور که میبنید نام ویژگی ها همچنان در کد تکرار میشوند. به طور مثال در قطعه کد بالا user.address سه بار تکرار شده است.

و حالا در نهایت زنجیره اختیاری آمده است که ما را نجات دهد!

زنجیره اختیاری

زنجیره ای اختیاری ?. بررسی را متوقف میکند اگر مقدار قبل از قسمت ?. برابر با undefined یا null باشد و مقدار undefined را برمیگرداند.

یا به عبارت دیگر value?.prop :

  • برابر است با value.prop اگر value‍ وجود داشته باشد
  • در غیر اینصورت (زمانی که value برابر با undefined/null است) مقدار undefined را برمیگرداند.

.? این یک دسترسی مطمئن به user.address.street است:

let user = {}; // کاربر بدون آدرس
alert( user?.address?.street ); // undefined (بدون خطا)

حالا کد خیلی کوتاه‌تر و تمیزتر است و بدون هیچ تکرار اضافه‌ای :)

خواندن ویژگی(property) آدرس با user?.address کار خواهد کرد حتی زمانی هم که شی(آبجکت) user وجود ندارد :

let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined

لطفا توجه داشته باشید : سینتکس ?. مقدارهای قبلی را اختیاری میکند نه مقدارهای جلوی آن را.

در مثال بالا user?. به user مقدار null/undefined خواهد داد.

در مثال بالا user?.address.street فقط به user‍ اجازه میدهد که null/undefined باشد. مثلا در این کد user?.address.street.name عبارت ‍.? اجازه میدهد که user برابر با null/undefined باشد. این همه کاری است که انجام میدهد.
ویژگی های جلویی به سبک معمولی به ویژگی ها دسترسی دارند.اگر ما میخواهیم بعضی از ویژگی ها را اختیاری کنیم میتوانیم تعداد بیشتری از . را با .? جایگزین کنیم

از طرف دیگر ، اگر ‍‍user وجود داشته باشد ، پس باید ویژگی user.address داشته باشد ، در غیر این صورت user؟.address.streetدر نقطه دوم خطا می دهد.

ما باید از `?.` فقط زمانی استفاده کنیم که عدم وجود چیزی اشکالی ندارد.برای مثال اگر طبق منطق و لاجیک ما باید شی(object)`user` وجود داشته باشد ولی address اختیاری است. پس ما باید اینگونه بنویسیم user.address?.street نه user?.address?.street
بنابراین ، اگر تصادفاً به دلیل اشتباهی ‍user برابر با undefined باشد، شاهد یک خطای برنامه نویسی در مورد آن خواهیم بود و آن را برطرف خواهیم کرد.

برای زنجیره اختیاری باید متغیر حتما تعریف شده باشد ( let/const/var user یا توابع ). زنجیره ای اختیاری فقط برای متغیرهای تعریف شده کار می کند.

// ReferenceError: user is not defined
user?.address;

از زنجیره اختیاری میتوان برای صدا زدن توابع هم استفاده کرد :

let userAdmin = {
    admin() {
    alert(&quotI am admin&quot);
    }
};
let userGuest = {};
userAdmin.admin?.(); // I am admin
userGuest.admin?.(); // هیچی (هیچ متدی نیست)

اگر ما می‌خواهیم از براکت به جای نقطه برای دسترسی به ویژگی(property) استفاده کنیم زنجیره اختیاری برای آن حالت هم کارایی دارد.

let user1 = {
 firstName: &quotJohn&quot 
};  

let user2 = null;  
let key = &quotfirstName&quot

alert( user1?.[key] ); // John 
alert( user2?.[key] ); // undefined 
alert( user1?.[key]?.something?.not?.existing); // undefined

خلاصه

سینتکس ?. سه حالت دارد:

  • حالت اول: obj?.prop - مقدار ‍‍obj.prop را برمیگرداند اگر obj وجود داشته باشد در غیر اینصورت مقدار undefined را برمیگرداند
  • حالت دوم: [obj?.[prop - مقدار ‍‍[obj.[prop را برمیگرداند اگر obj وجود داشته باشد در غیر اینصورت مقدار undefined را برمیگرداند
  • حالت سوم: ()obj.method() - ‍‍obj?.method را صدا میزند اگر obj وجود داشته باشد در غیر اینصورت مقدار undefined را برمیگرداند

همانطور که می بینیم ، همه آنها ساده و آسان برای استفاده هستند. ?. سمت چپ را از نظر null/undefined بررسی می کند و اجازه می دهد تا ارزیابی ادامه یابد اگر برابر باnull/undefined نباشد.

زنجیر ?. امکان دسترسی به خواص تودرتو را هم فراهم میکند.

با این حال هنوز ما باید ?. را با دقت اعمال کنیم ، فقط درصورتی قابل قبول است که سمت چپ ممکن است وجود نداشته باشد.با این حال خطاهای برنامه نویسی را از ما مخفی نمیکند اگر آنها اتفاق بیافتند.