التسلسل الاختياري .?
هو طريقة لتجنب الأخطاء التي تهدف للوصول إلى خصائص أو حقول غرض (كائن) ما، حتى إذا لم تكن الخصائص الوسيطة موجودة.
المشكلة
إذا كنت قد بدأت للتو في قراءة هذه البرنامج التعليمي الخاصّ بـِ JavaScript، فربما لم تواجه هذه المشكلة من قبل ولكنها شائعة جداً.
فعلى سبيل المثال، يمتلك بعض مستخدمين موقعنا عناويناً، ولكن بعضاً من هؤلاء المستخدمين لم يقوموا بحفظها ضمن ملفاتهم الشخصية. بالتالي لا يمكننا كتابة التعبير التالي من دون حدوث خطأ user.address.street
وذلك من أجل عرض اسم الشارع الخاص بالعنوان:
let user = {}; // قد لا يملك المستخدم عنواناً، كهذا الغرض على سبيل المثال
alert(user.address.street); // وبالتالي يحدث الخطأ عند محاولة الوصول للخواص أو الحقول ضمنه
أو عند الحديث عن تطوير مواقع الويب مثلاً، قد نودّ أحياناً الحصول على معلومات خاصة بعنصر من عناصر الصفحة والتي قد لا تكون موجودة بالأصل، فمثلاً:
// يحدث الخطأ إذا كانت نتيجة التابع أو الطريقة querySelector(...) هي null
let html = document.querySelector('.my-element').innerHTML;
قبل ظهور التركيب .?
في اللغة، كنا نستخدم العامل &&
لحل هذه المشكلة.
على سبيل المثال:
let user = {}; // غرض لمستخدم لا يملك عنوان
alert( user && user.address && user.address.street ); // يظهر لنا undefined من دون حدوث خطأ
وعلى الرغم من أن إضافة العامل &&
على طول المسار المطلوب للوصول للخاصية المناسبة يضمن وجود هذه الخاصية وعدم وقوع أخطاء، إلا أنه مرهق في الكتابة.
التسلسل الاختياري (غير الإجباري)
يؤدي التسلسل الاختياري .?
إلى إيقاف تقييم الكود البرمجي وإرجاع undefined
إذا كانت قيمة الجزء الموجود قبل (أيسر) التركيب .?
هي null
أو undfined
.
وللإيجاز، سنقول ضمن هذه المقالة أن شيئاً ما “موجود” إذا لم تكن قيمته null
ولم تكن undefined
كذلك.
وأما الطريقة الآمنة للوصول لـِ user.address.street
هي:
let user = {}; // غرض المستخدم التالي لا يملك خاصية العنوان
alert( user?.address?.street ); // سيظهر لنا بدون حدوث خطأ undefined
وبالتالي سيبقى التعبير الخاص بقراءة عنوان المستخدم user?.address
يعمل حتى لو لم يكن غرض المستخدم موجوداً بالأصل:
let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
يرجى ملاحظة أن التركيب أو الشقّ .?
يعمل بشكل صحيح حيث يتم وضعه بالضبط، وليس بعد ذلك المكان الذي تم وضعه فيه.
في السطرين الأخيرين، سيتوقف تقييم الكود البرمجي بشكل فوري بعد الشقّ .?user
ولا يستمر أبداً للخصائص التي تليه.
يقوم التسلسل الاختياري فقط باختبار القيم null/undefined
، ولا يتداخل مع ميكانيكية أي من اللغات الأخرى.
ولكن إذا كان الغرض user
موجوداً بالفعل، فيجب أن تكون الخصائص الوسيطة موجودة ونقصد بالخصائص الوسيطة user.address
مثلاً.
يجب أن نستخدم التركيب .?
فقط عندما يكون هناك غرض، كائن أو خاصية غير موجودة بالأصل.
فعلى سبيل المثال، ووفقاً للمنطق المتعلق بالأمثلة السابقة، يجب أن يكون غرض المستخدم user
موجوداً بالأصل، ولكن الخاصية address
هي اختيارية وقد لا تكون موجودة، وبالتالي التعبير user.address?.street
سيكون أفضل من إضافة التركيب .?
للتحقق من كل خاصية أو حقل تابع للغرض user?.address?.street
.
وبالتالي، إذا كان غرض المستخدم user
غير معرف بسبب خطأ ما، فسوف نعرف عن هذا الخطأ ونصلحه. وإلا ستتسبب هذه الطريقة بإسكات الأخطاء البرمجية وقد لا يكون ذلك مناسباً، بل سيصبح من الصعب تصحيح هذه الأخطاء وكشفها.
.?
يجب أن يكون معرّفاًإذا لم يتمّ تعريف المتحول user
، سيؤدي التعبير user?.anything
إلى حصول خطأ:
// ReferenceError: user is not defined
user?.address;
اختصار الطرق (Short-circuiting)
كما تمّ ذكره آنفاً، يقوم التركيب .?
بإيقاف عملية تقييم الكود البرمجي (يختصر الطريق) إذا لم يكن القسم اليساري (على يسار التركيب) موجوداً.
لذلك، وإذا كان هنالك أي استدعاءات لتوابع، لا يتم استدعائها أو تنفيذها:
let user = null;
let x = 0;
user?.sayHi(x++); // لا يحدث شيء
alert(x); // لا يتم زيادة القيمة 0
حالات أخرى ().?، [].?
لا يقتصر التسلسل الاختياري .?
في عمله على المتحولات فقط فهو ليس بعامل (رياضي) كالجمع والطرح، بل هو تركيب بنيوي يعمل أيضاً على التوابع والأقواس المربعة (أقواس المصفوفات).
على سبيل المثال، يمكن استخدام التركيب ().?
لاستدعاء تابع قد لا يكون معرّفاً بالأصل.
في المثال أدناه، يمتلك بعض أغراض المستخدمين الطريقة (method) أو التابع المُسمى admin
وبعضهم الآخر لا يمتلك:
let user1 = {
admin() {
alert("I am admin");
}
}
let user2 = {};
user1.admin?.(); // I am admin
user2.admin?.();
لقد استخدمنا في السطرين السابقين علامة النقطة .
للوصول للتابع admin
(مع عدم وجود إشارة الاستفهام) وذلك لأن غرض المستخدم user
يجب أن يكون موجوداً من قبل ليكون من الآمن قراءة التعابير منه.
وبالتالي سيقوم التعبير ().?
بفحص الجزء اليساري، فإذا كان التابع admin
معرّفاً، فيعمل التعبير (من أجل user1
)، وإلا (من أجل user2
) فستتوقف عملية التقييم من دون حدوث أخطاء.
في حال الرغبة باستخدام الأقواس المربّعة []
بدلاً من النقطة .
للوصول للخواص ضمن غرض أو كائن ما، سيفي التعبير [].?
بالغرض أيضاً. وبشكل مشابه للحالات السابقة، يسمح هذا التعبير بشكل آمن قراءةَ خاصية أو حقل ضمن غرض معيّن قد لا يكون موجوداً.
let user1 = {
firstName: "John"
};
let user2 = null; // على سبيل المثال، لم يكن بإمكاننا المصادقة على وجود مستخدم ما (القيام بعملية تسجيل الدخول له)
let key = "firstName";
alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
alert( user1?.[key]?.something?.not?.existing); // undefined
كذلك يمكننا استخدام التركيب .?
مع التعبير delete
:
delete user?.name; // سيقوم بحذف اسم المستخدم في حال كان غرض المستخدم موجوداً
.?
للقراءة والحذف الآمن (بدون وقوع أخطاء)، ولكن ليس مع الكتابة (الإسناد)لا يمكن استخدام تركيب التسلسل الاختياري .?
في الطرف اليساري لعملية الإسناد:
// فإذا حاولنا إسناد قيمة معينة لاسم المستخدم إذا كان المستخدم موجوداً
user?.name = "John"; // فسيحدث خطأ، لأن هذه الطريقة لا تعمل
// لأنه سيتم تقييمها على أن
// undefined = "John"
الخلاصة
لتركيب التسلسل الاختياري .?
ثلاثة أشكال:
obj?.prop
– يردّ التعبيرobj.prop
قيمةً صحيحة إذا كان الغرضobj
موجوداً، وإلا يُعيدundefined
.obj?.[prop]
– يردّ التعبيرobj[prop]
قيمةً صحيحة إذا كان الغرضobj
موجوداً، وإلا يُعيدundefined
.()obj?.method
– يقوم باستدعاء()obj.method
إذا كان الغرضobj
موجوداً، وإلا يُعيدundefined
.
وكما نرى، جميع الطرق السابقة واضحة وسهلة الاستخدام. فالتركيب .?
يتحقق من الجزء الأيسر فيما إذا لم يكن null/undefined
ليسمح بعدها بإكمال عملية التقييم.
وإذا كان لدينا خصائص متداخلة فيما بينها، فيسمح تسلسل من التركيب .?
بقرائتها بشكلٍ آمن.
ومع ذلك، يجب علينا استخدام التركيب .?
بعناية (عدم الإفراط) وذلك فقط في حالة عدم تأكدنا من وجود الجزء اليساري للتركيب.
حتى لا يخفي أخطاء البرمجة التي نرتكبها أحياناً، وذلك في حال حدثت.
التعليقات
<code>
، وللكثير من السطور استخدم<pre>
، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)