{"id":"l-js-3-4","courseId":"javascript-fundamentals","lang":"ar","kind":"lesson","summary":"تقرأ البيانات التي قد تكون مفقودة بأمان باستخدام السلسلة الاختيارية (optional chaining) وعامل الـ nullish coalescing (<code dir=\"ltr\">??</code>)، وتختار Map للبحث والعدّ وSet للقيم الفريدة.","html":"<p>البيانات الحقيقية فيها ثغرات. بعض المصاريف لها ملاحظة وبعضها لا. وبعضها أُرفق به إيصال. والإعداد الذي لم يغيّره المستخدم قط لا يُحفظ أصلًا. على Pocket أن يتعامل مع كل هذا دون أن ينهار، ودون أن يخلط بين “لا شيء” وقيمة حقيقية مثل الصفر.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الخطأ الذي ستراه أكثر من غيره</h2>\n<p>قراءة خاصية غير موجودة في كائن أمر لا بأس به؛ فتحصل على <code dir=\"ltr\">undefined</code>. أما قراءة خاصية <strong>من <code dir=\"ltr\">undefined</code></strong> فليست كذلك:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expense</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">450</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.receipt);          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// undefined: fine</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">try</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.receipt.url);    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// reading .url of undefined</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">} </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (error) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(error.message);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p><code dir=\"ltr\">TypeError: Cannot read properties of undefined (reading 'url')</code> ربما يكون الخطأ الأكثر شيوعًا في JavaScript كلها. (الـ <code dir=\"ltr\">try</code>/<code dir=\"ltr\">catch</code> هنا موجودة فقط كي يستمر المثال في العمل فتقرأ الرسالة؛ والقسم 5 يشرحها كما ينبغي.) والرسالة تخبرك بالضبط بما حدث: شيء ما قبل <code dir=\"ltr\">.url</code> كان <code dir=\"ltr\">undefined</code>.</p>\n<p>وقبل أن تمدّ يدك إلى الحل، تذكّر الفرق بين القيمتين “الفارغتين”. <code dir=\"ltr\">undefined</code> تعني عادةً “لم يُضبط قط”: خاصية مفقودة، أو معامل لم يمرّره أحد، أو متغيّر بلا قيمة. أما <code dir=\"ltr\">null</code> فتُضبط عن قصد لتعني “فارغ عمدًا”. قد يخزّن Pocket القيمة <code dir=\"ltr\">receipt: null</code> لمصروف اختار المستخدم ألّا يرفق به إيصالًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">السلسلة الاختيارية: ?.</h2>\n<p>العامل <code dir=\"ltr\">?.</code> يقرأ الخاصية فقط إن لم يكن ما على يساره <code dir=\"ltr\">null</code> أو <code dir=\"ltr\">undefined</code>. فإن كان كذلك، يتوقّف التعبير كله ويعطي <code dir=\"ltr\">undefined</code> بدلًا من أن يرمي خطأ:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> withReceipt</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Train\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, receipt: { url: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/r/88.png\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> withoutReceipt</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withReceipt.receipt?.url);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withoutReceipt.receipt?.url);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withoutReceipt.tags?.[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// works with brackets</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(withoutReceipt.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">format</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">?.());         </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// and with calls</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>ضع <code dir=\"ltr\">?.</code> مباشرة بعد الجزء الذي قد يكون مفقودًا. ففي <code dir=\"ltr\">expense.receipt?.url</code> أنت تقول: “<code dir=\"ltr\">expense</code> يجب أن يكون موجودًا، أما <code dir=\"ltr\">receipt</code> فقد لا يكون”.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>نثر ?. في كل مكان</span></p>\n<p><code dir=\"ltr\">a?.b?.c?.d</code> “تُصلح” كل انهيار، وهنا المشكلة. فإن كان <code dir=\"ltr\">expense</code> نفسه يجب أن يكون موجودًا دائمًا ثم اختفى فجأة، فأنت تريد خطأً صاخبًا يشير إلى الخلل الحقيقي، لا <code dir=\"ltr\">undefined</code> صامتة تظهر بعد ثلاث شاشات على شكل تسمية فارغة. استخدم <code dir=\"ltr\">?.</code> فقط مع البيانات الاختيارية بطبيعة تصميمها.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">القيم الافتراضية بـ ??</h2>\n<p><code dir=\"ltr\">?.</code> تعطيك <code dir=\"ltr\">undefined</code>، وفي العادة تريد قيمة بديلة. تعرّفت على <code dir=\"ltr\">||</code> للقيم الافتراضية في القسم 2، ومعها عيبها: إنها تلجأ إلى البديل مع كل قيمة falsy، بما في ذلك <code dir=\"ltr\">0</code> و<code dir=\"ltr\">&quot;&quot;</code>. أما عامل <strong>nullish coalescing</strong> <code dir=\"ltr\">??</code> (أي “البديل عند غياب القيمة”) فلا يلجأ إلى البديل إلا مع <code dir=\"ltr\">null</code> و<code dir=\"ltr\">undefined</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"2\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> settings</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { dailyLimit: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, nickname: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.dailyLimit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">||</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 5000: the user's 0 is lost</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.dailyLimit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 0: kept</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.nickname </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"friend\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// \"\": kept</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings.currency </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// missing: default used</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>استخدم <code dir=\"ltr\">??</code> للقيم الافتراضية. ولا تستخدم <code dir=\"ltr\">||</code> إلا حين تريد فعلًا استبدال النصوص الفارغة والأصفار أيضًا. ويجتمع العاملان مع <code dir=\"ltr\">?.</code> بشكل طبيعي:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"3\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expense</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> receiptUrl</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense.receipt?.url </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"No receipt\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(receiptUrl);</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>وهناك أيضًا صيغة إسناد، <code dir=\"ltr\">settings.currency ??= &quot;USD&quot;</code>، تضبط الخاصية فقط إن كانت حاليًا <code dir=\"ltr\">null</code> أو <code dir=\"ltr\">undefined</code>.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Map: البحث بأي مفتاح</h2>\n<p>في الدرس السابق استخدمت كائنًا عاديًا لحساب مجاميع الفئات. الكائنات تعمل جيدًا مع السجلّات ذات أسماء الخصائص المعروفة. أما المجموعة التي تكبر مع البيانات، ومفاتيحها قيم لا تعرفها مسبقًا، فلها في JavaScript بنية مخصّصة: <strong>Map</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"4\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> counts</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> category</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fun\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category, (counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">get</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">get</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">has</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"rent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), counts.size);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">count</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> counts) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">category</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}: ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">count</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre><div class=\"run-output\" data-run-output=\"4\" hidden></div></div>\n<p><code dir=\"ltr\">set(key, value)</code> تخزّن، و<code dir=\"ltr\">get(key)</code> تقرأ (أو تعطي <code dir=\"ltr\">undefined</code>)، و<code dir=\"ltr\">has(key)</code> تفحص، و<code dir=\"ltr\">delete(key)</code> تحذف، و<code dir=\"ltr\">size</code> تعدّ المدخلات. وحلقة <code dir=\"ltr\">for...of</code> تعطيك أزواج <code dir=\"ltr\">[key, value]</code> بترتيب إضافتها الأول.</p>\n<p>لماذا لا تستخدم كائنًا دائمًا؟ الـ Map تقبل <strong>أي قيمة مفتاحًا</strong>، بما في ذلك الأرقام والكائنات، بينما يحوّل الكائن كل مفتاح إلى نص. ولها <code dir=\"ltr\">size</code> حقيقية. وليست لها خصائص موروثة تتعثّر بها: فالكائن الذي مفاتيحه نصوص يكتبها المستخدم قد يتصادم مع أسماء مدمجة مثل <code dir=\"ltr\">constructor</code>. والخيار الافتراضي الجيد: <strong>الكائنات للسجلّات ذات الحقول الثابتة، والـ Maps لجداول البحث المبنية من البيانات.</strong></p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Set: كل قيمة مرة واحدة</h2>\n<p>الـ <strong>Set</strong> مجموعة تظهر فيها كل قيمة مرة واحدة على الأكثر. وإضافة قيمة موجودة أصلًا لا تفعل شيئًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"5\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> categories</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fun\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unique</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(categories);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(unique.size, unique.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">has</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fun\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unique.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);               </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// already there: no change</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unique]);         </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// back to an array, in first-seen order</span></span></code></pre><div class=\"run-output\" data-run-output=\"5\" hidden></div></div>\n<p><code dir=\"ltr\">[...new Set(list)]</code> هي الطريقة المعتمدة في سطر واحد لإزالة التكرار من مصفوفة. و<code dir=\"ltr\">has</code> أسرع بكثير من <code dir=\"ltr\">includes</code> على مصفوفة كبيرة، لأن الـ Set لا تبحث عنصرًا عنصرًا. وقد أضافت الإصدارات الحديثة من JavaScript عمليات على المجموعات مثل <code dir=\"ltr\">union</code> و<code dir=\"ltr\">intersection</code>، وهي متاحة في كل المتصفّحات الحالية.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الـ Sets تقارن الكائنات بالمرجع</span></p>\n<p>تعامل الـ Set (ومفاتيح الـ Map) كائنين على أنهما الشيء نفسه فقط إن كانا الكائن نفسه، وفق قاعدة المرجع نفسها التي تتبعها <code dir=\"ltr\">===</code>. فكائنان منفصلان <code dir=\"ltr\">{ label: &quot;Coffee&quot; }</code> هما مدخلان مختلفان. أزِل تكرار الكائنات بالاعتماد على معرّف بدلًا من ذلك، مثلًا بـ Set من قيم <code dir=\"ltr\">e.id</code>.</p>\n</aside>\n<p>بهذا تكتمل أدوات Pocket للتعامل مع البيانات: المصفوفات والكائنات والـ Maps والـ Sets، وعوامل التعامل مع القيم المفقودة. وفي القسم 4، يذهب كل ذلك إلى صفحة ويب حقيقية.</p>\n","toc":[{"id":"sec-1","text":"الخطأ الذي ستراه أكثر من غيره","level":2},{"id":"sec-2","text":"السلسلة الاختيارية: ?.","level":2},{"id":"sec-3","text":"القيم الافتراضية بـ ??","level":2},{"id":"sec-4","text":"Map: البحث بأي مفتاح","level":2},{"id":"sec-5","text":"Set: كل قيمة مرة واحدة","level":2}],"takeaways":["قراءة خاصية من <code dir=\"ltr\">undefined</code> أو <code dir=\"ltr\">null</code> ترمي TypeError؛ أما <code dir=\"ltr\">?.</code> فتتوقّف وتُرجع <code dir=\"ltr\">undefined</code> بدلًا من ذلك.","<code dir=\"ltr\">??</code> توفّر قيمة افتراضية فقط مع <code dir=\"ltr\">null</code> و<code dir=\"ltr\">undefined</code>، فتنجو القيم الحقيقية مثل <code dir=\"ltr\">0</code> و<code dir=\"ltr\">&quot;&quot;</code>، على عكس <code dir=\"ltr\">||</code>.","استخدم <code dir=\"ltr\">?.</code> فقط حيث تكون البيانات اختيارية فعلًا؛ فنثرها في كل مكان يُخفي أخطاء حقيقية.","الـ Map تخزّن أزواجًا من المفاتيح والقيم بمفاتيح من أي نوع، وتحفظ ترتيب الإضافة، ولها <code dir=\"ltr\">get</code> و<code dir=\"ltr\">set</code> و<code dir=\"ltr\">has</code> و<code dir=\"ltr\">size</code>.","الـ Set تخزّن كل قيمة مرة واحدة على الأكثر، لذلك تُزيل <code dir=\"ltr\">[...new Set(list)]</code> التكرار."],"further":[{"title":"Optional chaining (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"},{"title":"Nullish coalescing operator (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing"},{"title":"Map (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"},{"title":"Set (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set"}],"quiz":[{"q":"<p>قد يكون للمصروف كائن <code dir=\"ltr\">receipt</code> وقد لا يكون. أيّ تعبير يقرأ <code dir=\"ltr\">url</code> الإيصال دون أن يرمي خطأ أبدًا؟</p>","options":[{"text":"<code dir=\"ltr\">expense.receipt.url</code>","why":"حين تكون <code dir=\"ltr\">receipt</code> تساوي <code dir=\"ltr\">undefined</code>، فإن قراءة <code dir=\"ltr\">.url</code> منها ترمي “Cannot read properties of undefined”."},{"text":"<code dir=\"ltr\">expense?.receipt.url</code>","why":"<code dir=\"ltr\">?.</code> هنا تحمي من غياب <code dir=\"ltr\">expense</code>، لكن الجزء الاختياري هنا هو <code dir=\"ltr\">receipt</code>، وقراءة <code dir=\"ltr\">.url</code> منه ما زالت ترمي خطأ."},{"text":"<code dir=\"ltr\">expense.receipt?.url</code>","why":"صحيح. إن كانت <code dir=\"ltr\">receipt</code> تساوي <code dir=\"ltr\">null</code> أو <code dir=\"ltr\">undefined</code>، يتوقّف التعبير كله وتكون قيمته <code dir=\"ltr\">undefined</code>."}],"answer":2},{"q":"<p>نسبة البقشيش المحفوظة لدى مستخدم هي <code dir=\"ltr\">0</code>. ماذا يعطي <code dir=\"ltr\">const tip = saved.tip ?? 15;</code>؟</p>","options":[{"text":"<code dir=\"ltr\">0</code>","why":"صحيح. <code dir=\"ltr\">??</code> لا تلجأ إلى البديل إلا مع <code dir=\"ltr\">null</code> و<code dir=\"ltr\">undefined</code>، و0 ليست أيًّا منهما."},{"text":"<code dir=\"ltr\">15</code>","why":"هذا ما كانت ستعطيه <code dir=\"ltr\">||</code>، لأن 0 قيمة falsy. وتجنّب هذا بالضبط هو سبب وجود <code dir=\"ltr\">??</code>."},{"text":"<code dir=\"ltr\">undefined</code>","why":"للطرف الأيسر قيمة حقيقية، هي 0، فتُرجعها <code dir=\"ltr\">??</code>."}],"answer":0},{"q":"<p>تحتاج إلى عدّ المصاريف في كل فئة، ثم لاحقًا عرض الفئات بترتيب ظهورها الأول. أيّها الأنسب؟</p>","options":[{"text":"مصفوفة أسماء فئات، مع استخدام <code dir=\"ltr\">indexOf</code> للعثور على كل واحدة.","why":"البحث في مصفوفة مع كل مصروف يصبح بطيئًا، والأعداد تحتاج إلى مصفوفة ثانية يجب إبقاؤها متزامنة معها."},{"text":"Set من أسماء الفئات.","why":"الـ Set تسجّل أن القيمة موجودة، لكنها لا تستطيع تخزين عدد بجانبها."},{"text":"Map من الفئة إلى العدد.","why":"صحيح. <code dir=\"ltr\">get</code>/<code dir=\"ltr\">set</code> تحدّثان الأعداد مباشرة، والـ Map تتذكّر ترتيب الإضافة حين تمرّ عليها."},{"text":"نص فيه الفئات مفصولة بفواصل.","why":"النصوص غير قابلة للتعديل ويجب تقسيمها والبحث فيها كل مرة؛ إنها ليست مصمّمة للبحث."}],"answer":2},{"q":"<p>ماذا تُرجع <code dir=\"ltr\">new Set([&quot;food&quot;, &quot;travel&quot;, &quot;food&quot;, &quot;fun&quot;]).size</code>؟</p>","options":[{"text":"<code dir=\"ltr\">4</code>","why":"الـ Set تتجاهل القيم الموجودة فيها أصلًا، لذلك لا تُضاف “food” الثانية."},{"text":"<code dir=\"ltr\">3</code>","why":"صحيح. تحمل الـ Set القيم “food” و&quot;travel&quot; و&quot;fun&quot;، كلًّا منها مرة واحدة."},{"text":"<code dir=\"ltr\">2</code>","why":"لا تُحذف إلا “food” المكرّرة؛ أما “travel” و&quot;fun&quot; فتظهر كل منهما مرة واحدة وتبقيان."}],"answer":1}],"exercise":{"kind":"web","mode":"js","starter":{"js":"const expenses = [\n  { label: \"Coffee\", amount: 450, category: \"food\" },\n  { label: \"Train\", amount: 1220, category: \"travel\", receipt: { url: \"/r/88.png\" } },\n  { label: \"Lunch\", amount: 1350, category: \"food\", receipt: null },\n  { label: \"Gift\", amount: 5000 },\n];\n\n// The receipt's url, or \"No receipt\" (receipt may be missing or null)\nfunction receiptLabel(expense) {\n  return expense.receipt.url;\n}\n\n// settings may be undefined; a saved limit of 0 is a real choice\nfunction dailyLimit(settings) {\n  return settings.dailyLimit || 5000;\n}\n\n// A Map from category to how many expenses have it;\n// expenses without a category count as \"other\"\nfunction countByCategory(expenses) {\n}\n\n// Each category once, in the order first seen (missing ones as \"other\")\nfunction uniqueCategories(expenses) {\n  return expenses.map((e) => e.category);\n}\n\nconsole.log(receiptLabel(expenses[1]), dailyLimit({ dailyLimit: 0 }));\n"},"solution":{"js":"const expenses = [\n  { label: \"Coffee\", amount: 450, category: \"food\" },\n  { label: \"Train\", amount: 1220, category: \"travel\", receipt: { url: \"/r/88.png\" } },\n  { label: \"Lunch\", amount: 1350, category: \"food\", receipt: null },\n  { label: \"Gift\", amount: 5000 },\n];\n\nfunction receiptLabel(expense) {\n  return expense.receipt?.url ?? \"No receipt\";\n}\n\nfunction dailyLimit(settings) {\n  return settings?.dailyLimit ?? 5000;\n}\n\nfunction countByCategory(expenses) {\n  const counts = new Map();\n  for (const e of expenses) {\n    const category = e.category ?? \"other\";\n    counts.set(category, (counts.get(category) ?? 0) + 1);\n  }\n  return counts;\n}\n\nfunction uniqueCategories(expenses) {\n  return [...new Set(expenses.map((e) => e.category ?? \"other\"))];\n}\n\nconsole.log(receiptLabel(expenses[1]), dailyLimit({ dailyLimit: 0 }));\n"},"checks":[{"id":"receipt","test":"return receiptLabel(expenses[1]) === '/r/88.png' && receiptLabel(expenses[0]) === 'No receipt' && receiptLabel(expenses[2]) === 'No receipt';\n"},{"id":"limit","test":"return dailyLimit({ dailyLimit: 0 }) === 0 && dailyLimit({ dailyLimit: 2500 }) === 2500 && dailyLimit({}) === 5000 && dailyLimit(undefined) === 5000;\n"},{"id":"counts-map","test":"const m = countByCategory(expenses);\nreturn m instanceof Map && m.get('food') === 2 && m.get('travel') === 1 && m.get('other') === 1 && m.size === 3;\n"},{"id":"unique","test":"const u = uniqueCategories(expenses);\nreturn Array.isArray(u) && u.join() === 'food,travel,other';\n"}],"title":"تعامل مع البيانات المفقودة بأمان","prompt":"<p>بعض بيانات Pocket ناقصة. أكمل أربع دوال:</p>\n<ul>\n<li><code dir=\"ltr\">receiptLabel(expense)</code> تُرجع <code dir=\"ltr\">url</code> الإيصال، أو <code dir=\"ltr\">&quot;No receipt&quot;</code> حين تكون <code dir=\"ltr\">receipt</code> مفقودة أو <code dir=\"ltr\">null</code>.</li>\n<li><code dir=\"ltr\">dailyLimit(settings)</code> تُرجع <code dir=\"ltr\">settings.dailyLimit</code>، أو <code dir=\"ltr\">5000</code> حين لا يوجد حدّ. وقد يكون <code dir=\"ltr\">settings</code> نفسه <code dir=\"ltr\">undefined</code>، ويجب الإبقاء على حدّ محفوظ قيمته <code dir=\"ltr\">0</code>.</li>\n<li><code dir=\"ltr\">countByCategory(expenses)</code> تُرجع <strong>Map</strong> من الفئة إلى عدد المصاريف فيها. والمصاريف التي لا فئة لها تُعدّ ضمن <code dir=\"ltr\">&quot;other&quot;</code>.</li>\n<li><code dir=\"ltr\">uniqueCategories(expenses)</code> تُرجع مصفوفة فيها كل فئة مرة واحدة، بترتيب ظهورها الأول (والمفقودة على أنها <code dir=\"ltr\">&quot;other&quot;</code>).</li>\n</ul>\n","hints":["ضع <code dir=\"ltr\">?.</code> مباشرة بعد الجزء الذي قد يكون مفقودًا، ثم <code dir=\"ltr\">??</code> بعده للبديل: <code dir=\"ltr\">expense.receipt?.url ?? &quot;No receipt&quot;</code>.","<code dir=\"ltr\">||</code> تُضيّع الصفر. أما <code dir=\"ltr\">settings?.dailyLimit ?? 5000</code> فتحتفظ به وتنجو أيضًا من كون <code dir=\"ltr\">settings</code> تساوي undefined.","عُدّ باستخدام <code dir=\"ltr\">counts.set(category, (counts.get(category) ?? 0) + 1)</code>. وللقيم الفريدة، ابنِ Set من الفئات بعد تحويلها بـ map، ثم انشرها مجددًا في مصفوفة."],"explanation":"<p>تتوقّف <code dir=\"ltr\">?.</code> عن القراءة بمجرّد أن تصادف <code dir=\"ltr\">null</code> أو <code dir=\"ltr\">undefined</code> وتُنتج <code dir=\"ltr\">undefined</code>، ثم تستبدلها <code dir=\"ltr\">??</code> بالقيمة البديلة. ولأن <code dir=\"ltr\">??</code> لا تتفاعل إلا مع <code dir=\"ltr\">null</code> و<code dir=\"ltr\">undefined</code>، يمرّ الحدّ <code dir=\"ltr\">0</code> سليمًا، بينما كانت <code dir=\"ltr\">||</code> ستتخلّص منه. وتعدّ الـ Map كل فئة باستخدام <code dir=\"ltr\">get</code> و<code dir=\"ltr\">set</code>، و<code dir=\"ltr\">?? 0</code> تتعامل مع الظهور الأول للفئة. أما <code dir=\"ltr\">new Set(...)</code> فتُسقط الفئات المكرّرة مع الحفاظ على ترتيب الظهور الأول، ونشرها بـ <code dir=\"ltr\">[...set]</code> يعيدها مصفوفة.</p>\n","checkLabels":{"receipt":"<code dir=\"ltr\">receiptLabel</code> تُرجع الـ url أو “No receipt”","limit":"<code dir=\"ltr\">dailyLimit</code> تحتفظ بالصفر، وقيمتها الافتراضية 5000، وتنجو من غياب الإعدادات","counts-map":"<code dir=\"ltr\">countByCategory</code> تُرجع Map بالأعداد، مع “other” للفئات المفقودة","unique":"<code dir=\"ltr\">uniqueCategories</code> تعرض كل فئة مرة واحدة، بترتيب ظهورها الأول"}},"words":640,"readMinutes":4}