٢٥ مارس ٢٠٢١

التسلسل الاختياري (غير الإجباري) '.?'

إضافة حديثة
هذه إضافة حديثة إلى اللغه. المتصفحات القديمه ربما تتطلب polyfills.

The optional chaining ?. is a safe way to access nested object properties, even if an intermediate property doesn’t exist.

The “non-existing property” problem

إذا كنت قد بدأت للتو في قراءة هذه البرنامج التعليمي الخاصّ بـِ JavaScript، فربما لم تواجه هذه المشكلة من قبل ولكنها شائعة جداً.

As an example, let’s say we have user objects that hold the information about our users.

Most of our users have addresses in user.address property, with the street user.address.street, but some did not provide them.

In such case, when we attempt to get user.address.street, and the user happens to be without an address, we get an error:

let user = {}; // a user without "address" property

alert(user.address.street); // وبالتالي يحدث الخطأ عند محاولة الوصول للخواص أو الحقول ضمنه

That’s the expected result. JavaScript works like this. As user.address is undefined, an attempt to get user.address.street fails with an error.

In many practical cases we’d prefer to get undefined instead of an error here (meaning “no street”).

…And another example. In the web development, we can get an object that corresponds to a web page element using a special method call, such as document.querySelector('.elem'), and it returns null when there’s no such element.

// document.querySelector('.elem') is null if there's no element
let html = document.querySelector('.elem').innerHTML; // error if it's null

Once again, if the element doesn’t exist, we’ll get an error accessing .innerHTML of null. And in some cases, when the absence of the element is normal, we’d like to avoid the error and just accept html = null as the result.

How can we do this?

The obvious solution would be to check the value using if or the conditional operator ?, before accessing its property, like this:

let user = {};

alert(user.address ? user.address.street : undefined);

It works, there’s no error… But it’s quite inelegant. As you can see, the "user.address" appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required.

E.g. let’s try getting user.address.street.name.

We need to check both user.address and user.address.street:

let user = {}; // user has no address

alert(user.address ? user.address.street ? user.address.street.name : null : null);

That’s just awful, one may even have problems understanding such code.

Don’t even care to, as there’s a better way to write it, using the && operator:

let user = {}; // غرض لمستخدم لا يملك عنوان

alert( user.address && user.address.street && user.address.street.name ); // undefined (no error)

AND’ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn’t ideal.

As you can see, property names are still duplicated in the code. E.g. in the code above, user.address appears three times.

That’s why the optional chaining ?. was added to the language. To solve this problem once and for all!

التسلسل الاختياري (غير الإجباري)

The optional chaining ?. stops the evaluation if the value before ?. is undefined or null and returns undefined.

وللإيجاز، سنقول ضمن هذه المقالة أن شيئاً ما “موجود” إذا لم تكن قيمته null ولم تكن undefined كذلك.

In other words, value?.prop:

  • works as value.prop, if value exists,
  • otherwise (when value is undefined/null) it returns undefined.

Here’s the safe way to access user.address.street using ?.:

let user = {}; // غرض المستخدم التالي لا يملك خاصية العنوان

alert( user?.address?.street ); // سيظهر لنا بدون حدوث خطأ undefined

The code is short and clean, there’s no duplication at all.

Reading the address with user?.address works even if user object doesn’t exist:

let user = null;

alert( user?.address ); // undefined
alert( user?.address.street ); // undefined

يرجى ملاحظة أن التركيب أو الشقّ .? يعمل بشكل صحيح حيث يتم وضعه بالضبط، وليس بعد ذلك المكان الذي تم وضعه فيه.

E.g. in user?.address.street.name the ?. allows user to safely be null/undefined (and returns undefined in that case), but that’s only for user. Further properties are accessed in a regular way. If we want some of them to be optional, then we’ll need to replace more . with ?..

ولكن إذا كان الغرض user موجوداً بالفعل، فيجب أن تكون الخصائص الوسيطة موجودة ونقصد بالخصائص الوسيطة user.address مثلاً.

For example, if according to our coding logic user object must exist, but address is optional, then we should write user.address?.street, but not user?.address?.street.

So, if user happens to be undefined due to a mistake, we’ll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug.

````warn header="المتحول الواقع قبل التركيب `.?` يجب أن يكون معرّفاً"
إذا لم يتمّ تعريف المتحول `user`، سيؤدي التعبير `user?.anything` إلى حصول خطأ:

```js run
// ReferenceError: user is not defined
user?.address;

The variable must be declared (e.g. let/const/var user or as a function parameter). The optional chaining works only for declared variables.

## اختصار الطرق (Short-circuiting)

كما تمّ ذكره آنفاً، يقوم التركيب `.?` بإيقاف عملية تقييم الكود البرمجي (يختصر الطريق) إذا لم يكن القسم اليساري (على يسار التركيب) موجوداً.

So, if there are any further function calls or side effects, they don't occur.

For instance:

```js run
let user = null;
let x = 0;

user?.sayHi(x++); // no "sayHi", so the execution doesn't reach x++

alert(x); // لا يتم زيادة القيمة 0
```

## Other variants: ?.(), ?.[]

لا يقتصر التسلسل الاختياري `.?` في عمله على المتحولات فقط فهو ليس بعامل (رياضي) كالجمع والطرح، بل هو تركيب بنيوي يعمل أيضاً على التوابع والأقواس المربعة (أقواس المصفوفات).

على سبيل المثال، يمكن استخدام التركيب `().?` لاستدعاء تابع قد لا يكون معرّفاً بالأصل.

في المثال أدناه، يمتلك بعض أغراض المستخدمين الطريقة (method) أو التابع المُسمى `admin` وبعضهم الآخر لا يمتلك:

```js run
let userAdmin = {
  admin() {
    alert("I am admin");
  }
};

let userGuest = {};

userAdmin.admin?.(); // I am admin

userGuest.admin?.(); // nothing (no such method)
```

Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the user object exists, so it's safe read from it.

Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors.

في حال الرغبة باستخدام الأقواس المربّعة `[]` بدلاً من النقطة `.` للوصول للخواص ضمن غرض أو كائن ما، سيفي التعبير `[].?` بالغرض أيضاً. وبشكل مشابه للحالات السابقة، يسمح هذا التعبير بشكل آمن قراءةَ خاصية أو حقل ضمن غرض معيّن قد لا يكون موجوداً.

```js run
let key = "firstName";

let user1 = {
  firstName: "John"
};

let user2 = null;

alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
```

كذلك يمكننا استخدام التركيب `.?` مع التعبير `delete`:

```js run
delete user?.name; // سيقوم بحذف اسم المستخدم في حال كان غرض المستخدم موجوداً
```

````warn header="We can use `?.` for safe reading and deleting, but not writing"
The optional chaining `?.` has no use at the left side of an assignment.

For example:
```js run
let user = null;

user?.name = "John"; // فسيحدث خطأ، لأن هذه الطريقة لا تعمل
// لأنه سيتم تقييمها على أن
// undefined = "John"
```

It's just not that smart.

Summary

The optional chaining ?. syntax has three forms:

  1. obj?.prop – returns obj.prop if obj exists, otherwise undefined.
  2. obj?.[prop] – returns obj[prop] if obj exists, otherwise undefined.
  3. obj.method?.() – calls obj.method() if obj.method exists, otherwise returns undefined.

وكما نرى، جميع الطرق السابقة واضحة وسهلة الاستخدام. فالتركيب .? يتحقق من الجزء الأيسر فيما إذا لم يكن null/undefined ليسمح بعدها بإكمال عملية التقييم.

وإذا كان لدينا خصائص متداخلة فيما بينها، فيسمح تسلسل من التركيب .? بقرائتها بشكلٍ آمن.

Still, we should apply ?. carefully, only where it’s acceptable that the left part doesn’t exist. So that it won’t hide programming errors from us, if they occur.

خريطة الدورة التعليمية

التعليقات

إقرأ هذا قبل أن تضع تعليقًا…
  • إذا كان لديك اقتراحات أو تريد تحسينًا - من فضلك من فضلك إفتح موضوعًا فى جيتهاب أو شارك بنفسك بدلًا من التعليقات.
  • إذا لم تستطع أن تفهم شيئّا فى المقال - وضّح ماهو.
  • إذا كنت تريد عرض كود استخدم عنصر <code> ، وللكثير من السطور استخدم <pre>، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)