{"id":"l-js-5-2","courseId":"javascript-fundamentals","lang":"ar","kind":"lesson","summary":"تعرّف الأصناف (classes) بـ constructor وmethods وgetters وحقول خاصة، وتُنشئ instances مستقلة بـ new، وتتجنّب خطأ ضياع this الكلاسيكي حين تمرّر الـ methods على أنها callbacks.","html":"<p>انظر إلى ما يفعله كود Pocket بمصاريفه: متغيّر يحمل المصفوفة، ونحو اثنتي عشرة دالة تأخذ كل منها تلك المصفوفة، وتفعل شيئًا، وتُرجع نتيجة. البيانات والعمليات عليها منفصلة، فلا شيء يمنع سطر كود شاردًا من دفع مصروف مشوّه مباشرة إلى المصفوفة. أما <strong>الصنف</strong> (class) فيحزم البيانات مع الدوال المسموح لها بالعمل عليها، ويستطيع أن يجعل البيانات نفسها بعيدة عن متناول الخارج.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">تعريف صنف</h2>\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\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ExpenseStore</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  #expenses</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [];</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  #nextNumber</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  constructor</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">initial</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</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\"> expense</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> initial) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense);</span></span>\n<span class=\"line\"><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:#8250DF;--shiki-dark:#D2A8FF\">  add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">amount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">category</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"other\"</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\"> expense</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { id: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`exp-${</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">#nextNumber</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">++</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, label, amount, category };</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">push</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense;</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:#8250DF;--shiki-dark:#D2A8FF\">  remove</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#expenses </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!==</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> id);</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:#CF222E;--shiki-dark:#FF7B72\">  get</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> total</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">sum</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sum </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.amount, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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:#8250DF;--shiki-dark:#D2A8FF\">  list</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#expenses];</span></span>\n<span class=\"line\"><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:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> store</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\"> ExpenseStore</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\">, category: </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\">store.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</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\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1220</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, category: </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:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(store.total, store.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">list</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.id));</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>هناك الكثير هنا، فخذه قطعة قطعة.</p>\n<p><code dir=\"ltr\">#expenses = []</code> تصرّح بـ <strong>حقل خاص</strong> (private field). كل instance (نسخة) تحصل على <code dir=\"ltr\">#expenses</code> خاص بها، وعلامة <code dir=\"ltr\">#</code> تجعله خاصًا: لا يستطيع قراءته أو الكتابة فيه إلا الكود الموجود داخل جسم الصنف. وفي الخارج، <code dir=\"ltr\">store.#expenses</code> خطأ في الصياغة. ومن يريد تغيير القائمة عليه أن يمرّ عبر <code dir=\"ltr\">add</code> و<code dir=\"ltr\">remove</code>، اللتين تضمنان أن يحصل كل مصروف على معرّف وفئة.</p>\n<p>الـ <code dir=\"ltr\">constructor</code> (الباني) method خاصة تُنفَّذ حين تكتب <code dir=\"ltr\">new ExpenseStore(...)</code>. ومهمتها تهيئة الكائن الجديد. وهي هنا تضيف أي مصاريف ابتدائية عبر <code dir=\"ltr\">add</code>، فتخضع للقواعد نفسها.</p>\n<p><code dir=\"ltr\">add</code> و<code dir=\"ltr\">remove</code> و<code dir=\"ltr\">list</code> هي <strong>methods</strong> (دوال الكائن). وداخل الـ method، تشير <code dir=\"ltr\">this</code> إلى الكائن الذي استُدعيت عليه: في <code dir=\"ltr\">store.add(...)</code>، تكون <code dir=\"ltr\">this</code> هي <code dir=\"ltr\">store</code>.</p>\n<p><code dir=\"ltr\">get total()</code> هو <strong>getter</strong>. تقرؤه كأنه خاصية، <code dir=\"ltr\">store.total</code>، دون أقواس، لكنه ينفّذ كودًا في كل مرة. فالمجموع المخزّن في حقل قد يصبح قديمًا حين يُحذف مصروف؛ أما الـ getter فيُحسب دائمًا من البيانات الحالية.</p>\n<p><code dir=\"ltr\">list()</code> تُرجع نسخة، <code dir=\"ltr\">[...this.#expenses]</code>، لا المصفوفة نفسها. فإرجاع المصفوفة الحقيقية سيسمح للمُستدعين بـ <code dir=\"ltr\">push</code> فيها، ويُبطل كل الحماية.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ Instances: مخطّط واحد وكائنات كثيرة</h2>\n<p><code dir=\"ltr\">new</code> تُنشئ كائنًا جديدًا، وتنفّذ الـ constructor و<code dir=\"ltr\">this</code> تشير إليه، ثم تُرجعه. لكل instance حقولها الخاصة؛ أما الـ methods فتُعرَّف مرة واحدة على الصنف وتُتشارك.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 660 230\" role=\"img\" aria-labelledby=\"l-js-5-2-t1\"><defs><marker id=\"arrow-l-js-5-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-js-5-2-t1\">يعرّف الصنف ExpenseStore الـ methods‏ add وremove وlist والـ getter‏ total. ولكل من الـ instances‏ home وwork مصفوفة expenses خاصة بها، وكلتاهما تستخدمان الـ methods المشتركة.</title>\n  <rect class=\"d-box-primary\" x=\"230\" y=\"14\" width=\"200\" height=\"86\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"330\" y=\"40\" text-anchor=\"middle\">class ExpenseStore</text>\n  <text class=\"d-code\" x=\"330\" y=\"64\" text-anchor=\"middle\">add, remove, list</text>\n  <text class=\"d-code\" x=\"330\" y=\"86\" text-anchor=\"middle\">get total</text>\n  <rect class=\"d-box\" x=\"40\" y=\"150\" width=\"230\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"155\" y=\"176\" text-anchor=\"middle\">home</text>\n  <text class=\"d-label-muted\" x=\"155\" y=\"200\" text-anchor=\"middle\">#expenses: 12 عنصرًا</text>\n  <rect class=\"d-box\" x=\"390\" y=\"150\" width=\"230\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"505\" y=\"176\" text-anchor=\"middle\">work</text>\n  <text class=\"d-label-muted\" x=\"505\" y=\"200\" text-anchor=\"middle\">#expenses: 3 عناصر</text>\n  <path class=\"d-arrow\" d=\"M290 100 L180 146\" marker-end=\"url(#arrow-l-js-5-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M370 100 L480 146\" marker-end=\"url(#arrow-l-js-5-2-1)\"/>\n  <text class=\"d-label-muted\" x=\"330\" y=\"140\" text-anchor=\"middle\">new ExpenseStore()</text>\n</svg><figcaption>صنف واحد وinstances كثيرة لكل منها بياناتها</figcaption></figure>\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\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ExpenseStore</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  #expenses</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [];</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">expense</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">push</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense); }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  get</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> count</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#expenses.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</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:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> home</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\"> ExpenseStore</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\"> work</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\"> ExpenseStore</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">home.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Groceries\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">6400</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\">(home.count, work.count);</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>إن ذكّرك هذا بـ <code dir=\"ltr\">createBudget</code> من درس الـ closures، فهذا طبيعي. الـ closure والصنف ذو الحقول الخاصة يحلّان المشكلة نفسها: بيانات لا تستطيع لمسها إلا دوال معيّنة. الـ closures أخفّ مع دالة واحدة؛ والأصناف أوضح حين تتشارك عدّة عمليات البيانات نفسها.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">ضياع this</h2>\n<p><code dir=\"ltr\">this</code> لا تُثبَّت حين تكتب الـ method. بل تتحدّد في كل مرة <strong>تُستدعى</strong> فيها الدالة، بحسب ما يقع على يسار النقطة. <code dir=\"ltr\">store.add(x)</code> تستدعي <code dir=\"ltr\">add</code> و<code dir=\"ltr\">this</code> مضبوطة على <code dir=\"ltr\">store</code>. لكن إن مرّرت الـ method إلى مكان ما وحدها، فلن تكون هناك نقطة حين تُستدعى في النهاية:</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\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Greeter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Pocket\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  hello</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `Hello from ${</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</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>\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:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> g</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\"> Greeter</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\">(g.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">hello</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">());</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> detached</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> g.hello;</span></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\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">detached</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">());</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\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Error:\"</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>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> wrapped</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> g.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">hello</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\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">wrapped</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">());</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>الاستدعاء الأول ينجح لأن <code dir=\"ltr\">g</code> على يسار النقطة. أما <code dir=\"ltr\">detached()</code> فتُستدعى ولا شيء على يسار أي نقطة، فتكون <code dir=\"ltr\">this</code> داخلها <code dir=\"ltr\">undefined</code> (أجسام الأصناف تعمل دائمًا في الوضع الصارم)، وقراءة <code dir=\"ltr\">this.name</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>تمرير method على أنها callback</span></p>\n<p><code dir=\"ltr\">form.addEventListener(&quot;submit&quot;, store.add)</code> و<code dir=\"ltr\">items.forEach(store.add)</code> كلتاهما تقعان في هذا الخطأ. غلّف الاستدعاء بدالة سهمية، <code dir=\"ltr\">(e) =&gt; store.add(e)</code>، كي تُستدعى الـ method على الكائن الصحيح. الدوال السهمية ليست لها <code dir=\"ltr\">this</code> خاصة بها، ولهذا بالضبط هي آمنة هنا.</p>\n</aside>\n<p>يمكن للأصناف أيضًا أن تُبنى فوق أصناف أخرى باستخدام <code dir=\"ltr\">extends</code>، فترث methods الصنف الآخر وتضيف methods جديدة. ستستخدم ذلك مرة واحدة، في الدرس التالي، لتصنع نوع خطأ خاصًا بك. وفيما عدا ذلك، فالخيار الافتراضي الجيد في كود التطبيقات هو تفضيل الأصناف الصغيرة والدوال العادية على سلاسل الوراثة العميقة، التي يصعب تغييرها عادةً.</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>هل تحتاج إلى صنف أصلًا؟</span></p>\n<p>دوال Pocket المساعدة للمال أفضل على شكل دوال عادية: فهي لا تحمل أي بيانات. الجأ إلى الصنف حين تكون لديك حالة (state) وقواعد لتغييرها، مثل المخزن. وكثير من مشاريع JavaScript الممتازة تستخدم أصنافًا قليلة؛ لكن معرفة قراءتها أمر لا نقاش فيه، لأن واجهات المتصفّح البرمجية والمكتبات مليئة بها.</p>\n</aside>\n<p>صار مخزنك يرفض التغييرات السيئة من الخارج. والدرس التالي عمّا يحدث حين يسوء شيء ما رغم ذلك: الأخطاء، وكيف ترميها وتلتقطها عن قصد، وكيف تطارد الأخطاء البرمجية بأدوات DevTools في المتصفّح.</p>\n","toc":[{"id":"sec-1","text":"تعريف صنف","level":2},{"id":"sec-2","text":"الـ Instances: مخطّط واحد وكائنات كثيرة","level":2},{"id":"sec-3","text":"ضياع this","level":2}],"takeaways":["الصنف (class) مخطّط؛ و<code dir=\"ltr\">new ClassName(...)</code> تنفّذ الـ constructor وتُرجع instance جديدة لها بياناتها الخاصة.","الـ methods مشتركة بين كل الـ instances، وداخلها تشير <code dir=\"ltr\">this</code> إلى الـ instance التي استُدعيت عليها الـ method.","الحقول الخاصة (<code dir=\"ltr\">#expenses</code>) لا يمكن قراءتها إلا داخل جسم الصنف، وهذا يحمي بيانات الكائن كما تفعل الـ closures.","الـ getter (<code dir=\"ltr\">get total()</code>) يُقرأ كأنه خاصية لكنه يُحسب في كل مرة، فلا تصبح القيم المشتقّة قديمة أبدًا.","تمرير <code dir=\"ltr\">store.add</code> على أنها callback يُضيّع <code dir=\"ltr\">this</code>؛ غلّفها بدالة سهمية: <code dir=\"ltr\">(e) =&gt; store.add(e)</code>."],"further":[{"title":"Classes (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes"},{"title":"Private elements (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements"},{"title":"this (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this"}],"quiz":[{"q":"<p>ماذا يطبع هذا الكود؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><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\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Counter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  count</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>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  increment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.count</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">++</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> a</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\"> Counter</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\"> b</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\"> Counter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">a.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">increment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">a.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">increment</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\">(a.count, b.count);</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">2 2</code>","why":"الحقول تُنشأ لكل instance على حدة، لذلك لـ <code dir=\"ltr\">b</code> حقل <code dir=\"ltr\">count</code> خاص بها لم يزده أحد."},{"text":"<code dir=\"ltr\">1 1</code>","why":"استُدعيت <code dir=\"ltr\">increment</code> مرتين على <code dir=\"ltr\">a</code>، ولم تُستدعَ أبدًا على <code dir=\"ltr\">b</code>."},{"text":"<code dir=\"ltr\">0 0</code>","why":"<code dir=\"ltr\">this.count++</code> تغيّر الـ instance التي استُدعيت عليها، لذلك تزيد <code dir=\"ltr\">a.count</code>."},{"text":"<code dir=\"ltr\">2 0</code>","why":"صحيح. كل <code dir=\"ltr\">new Counter()</code> تُنشئ كائنًا منفصلًا له <code dir=\"ltr\">count</code> خاص به؛ الـ method مشتركة، أما البيانات فلا."}],"answer":3},{"q":"<p><code dir=\"ltr\">button.addEventListener(&quot;click&quot;, store.clear);</code> تجعل <code dir=\"ltr\">clear</code> ترمي TypeError عن الحقل الخاص <code dir=\"ltr\">#expenses</code> (“…from an object whose class did not declare it”) حين يُنقر الزر. لماذا؟</p>","options":[{"text":"لا يمكن استخدام methods الأصناف مستمعين للأحداث.","why":"يمكن ذلك، ما دامت تُستدعى بـ <code dir=\"ltr\">this</code> الصحيحة. المشكلة في طريقة تمرير الـ method."},{"text":"مُرّرت الـ method وحدها، لذلك حين يستدعيها المتصفّح لا تكون <code dir=\"ltr\">this</code> هي <code dir=\"ltr\">store</code>.","why":"صحيح. <code dir=\"ltr\">this</code> تتحدّد بطريقة استدعاء الدالة، والمتصفّح يستدعي المستمع و<code dir=\"ltr\">this</code> مضبوطة على العنصر، أي الزر هنا. اكتب <code dir=\"ltr\">() =&gt; store.clear()</code> كي تُستدعى على <code dir=\"ltr\">store</code>."},{"text":"<code dir=\"ltr\">store</code> أُنشئ بـ <code dir=\"ltr\">const</code> ولا يمكن تغييره.","why":"<code dir=\"ltr\">const</code> لا تمنع methods الكائن من تغيير حقوله. المشكلة هي ضياع <code dir=\"ltr\">this</code>."}],"answer":1},{"q":"<p>يحتفظ <code dir=\"ltr\">ExpenseStore</code> بقائمته في <code dir=\"ltr\">#expenses</code>. ماذا تفعل <code dir=\"ltr\">store.#expenses</code> المكتوبة خارج الصنف؟</p>","options":[{"text":"إنها خطأ في الصياغة؛ الحقول الخاصة لا يمكن الوصول إليها إلا داخل جسم الصنف.","why":"صحيح. المحرّك يفرض الخصوصية، لذلك على الكود الخارجي استخدام الـ methods العامة."},{"text":"تُرجع المصفوفة، لأن <code dir=\"ltr\">#</code> مجرّد عرف في التسمية.","why":"العرف الأقدم <code dir=\"ltr\">_expenses</code> كان مجرّد تلميح. أما <code dir=\"ltr\">#</code> فخصوصية حقيقية تفرضها اللغة."},{"text":"تُرجع <code dir=\"ltr\">undefined</code>.","why":"إنها لا تُنفَّذ أصلًا. استخدام <code dir=\"ltr\">#expenses</code> خارج الصنف يُرفض لحظة تحليل الكود."}],"answer":0}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// Pocket's data, unprotected: any code can push anything into it.\nclass ExpenseStore {\n  constructor() {\n    this.expenses = [];\n  }\n\n  add(expense) {\n    this.expenses.push(expense);\n  }\n}\n\nconst store = new ExpenseStore();\nstore.add({ label: \"Coffee\", amount: 450, category: \"food\" });\nconsole.log(store.expenses);\n"},"solution":{"js":"class ExpenseStore {\n  #expenses = [];\n  #nextNumber = 1;\n\n  add({ label, amount, category = \"other\" }) {\n    const expense = { id: `exp-${this.#nextNumber++}`, label, amount, category };\n    this.#expenses.push(expense);\n    return expense;\n  }\n\n  remove(id) {\n    this.#expenses = this.#expenses.filter((e) => e.id !== id);\n  }\n\n  list() {\n    return [...this.#expenses];\n  }\n\n  get total() {\n    return this.#expenses.reduce((sum, e) => sum + e.amount, 0);\n  }\n\n  totalFor(category) {\n    return this.#expenses\n      .filter((e) => e.category === category)\n      .reduce((sum, e) => sum + e.amount, 0);\n  }\n}\n\nconst store = new ExpenseStore();\nstore.add({ label: \"Coffee\", amount: 450, category: \"food\" });\nconsole.log(store.list(), store.total);\n"},"checks":[{"id":"add-returns","test":"const s = new ExpenseStore();\nconst a = s.add({ label: 'Coffee', amount: 450, category: 'food' });\nconst b = s.add({ label: 'Gift', amount: 5000 });\nreturn a && a.id === 'exp-1' && b.id === 'exp-2' && b.category === 'other' && a.label === 'Coffee' && a.amount === 450;\n"},{"id":"list-copy","test":"const s = new ExpenseStore();\ns.add({ label: 'Coffee', amount: 450, category: 'food' });\nconst l = s.list();\nl.push({ id: 'hack', label: 'x', amount: 1 });\nreturn Array.isArray(l) && s.list().length === 1;\n"},{"id":"private","test":"const s = new ExpenseStore();\ns.add({ label: 'Coffee', amount: 450, category: 'food' });\nreturn Object.keys(s).length === 0 && !Object.values(s).some(Array.isArray);\n"},{"id":"remove","test":"const s = new ExpenseStore();\ns.add({ label: 'A', amount: 1, category: 'x' });\nconst b = s.add({ label: 'B', amount: 2, category: 'x' });\ns.remove('exp-1');\nconst l = s.list();\nreturn l.length === 1 && l[0].id === b.id;\n"},{"id":"total-getter","test":"const s = new ExpenseStore();\ns.add({ label: 'A', amount: 450, category: 'food' });\ns.add({ label: 'B', amount: 1220, category: 'travel' });\nconst d = Object.getOwnPropertyDescriptor(ExpenseStore.prototype, 'total');\nconst before = s.total;\ns.remove('exp-1');\nreturn !!d && typeof d.get === 'function' && before === 1670 && s.total === 1220;\n"},{"id":"total-for","test":"const s = new ExpenseStore();\ns.add({ label: 'A', amount: 450, category: 'food' });\ns.add({ label: 'B', amount: 1220, category: 'travel' });\ns.add({ label: 'C', amount: 1350, category: 'food' });\nreturn s.totalFor('food') === 1800 && s.totalFor('fun') === 0;\n"},{"id":"independent","test":"const a = new ExpenseStore();\nconst b = new ExpenseStore();\na.add({ label: 'A', amount: 1 });\na.add({ label: 'B', amount: 2 });\nconst first = b.add({ label: 'C', amount: 3 });\nreturn b.list().length === 1 && first.id === 'exp-1' && a.total === 3;\n"}],"title":"مخزن يحمي بياناته","prompt":"<p>أعِد كتابة <code dir=\"ltr\">ExpenseStore</code> بحيث لا تتغيّر بياناته إلا عبر الـ methods الخاصة به:</p>\n<ul>\n<li>احتفظ بالمصاريف في <strong>حقل خاص</strong> <code dir=\"ltr\">#expenses</code>، كي لا تكون للـ instances أي خصائص عامة.</li>\n<li><code dir=\"ltr\">add({ label, amount, category })</code> تُنشئ مصروفًا له معرّف (<code dir=\"ltr\">&quot;exp-1&quot;</code> و<code dir=\"ltr\">&quot;exp-2&quot;</code>… يُعدّ لكل مخزن على حدة)، و<code dir=\"ltr\">category</code> قيمتها الافتراضية <code dir=\"ltr\">&quot;other&quot;</code>، وتخزّنه و<strong>تُرجعه</strong>.</li>\n<li><code dir=\"ltr\">remove(id)</code> تحذف المصروف صاحب ذلك المعرّف.</li>\n<li><code dir=\"ltr\">list()</code> تُرجع <strong>نسخة</strong> من مصفوفة المصاريف.</li>\n<li><code dir=\"ltr\">total</code> هو <strong>getter</strong> يُرجع مجموع كل المبالغ.</li>\n<li><code dir=\"ltr\">totalFor(category)</code> تُرجع المجموع لفئة واحدة (0 إن لم توجد).</li>\n</ul>\n","hints":["صرّح بالحقول في أعلى جسم الصنف: <code dir=\"ltr\">#expenses = [];</code> و<code dir=\"ltr\">#nextNumber = 1;</code>. وداخل الـ methods، استخدم <code dir=\"ltr\">this.#expenses</code>.","ابنِ المعرّف بـ <code dir=\"ltr\">`exp-${this.#nextNumber++}`</code>، وفكّك المعامل مع قيمة افتراضية: <code dir=\"ltr\">add({ label, amount, category = &quot;other&quot; })</code>.","<code dir=\"ltr\">get total() { return this.#expenses.reduce((sum, e) =&gt; sum + e.amount, 0); }</code>. و<code dir=\"ltr\">list()</code> تُرجع <code dir=\"ltr\">[...this.#expenses]</code>."],"explanation":"<p>الحقول الخاصة تعني أن الطريقة الوحيدة لتغيير القائمة هي عبر <code dir=\"ltr\">add</code> و<code dir=\"ltr\">remove</code>، فيُضمن لكل مصروف معرّف وفئة، ولا يستطيع الكود الخارجي دسّ كائن ناقص. وكل <code dir=\"ltr\">new ExpenseStore()</code> تحصل على <code dir=\"ltr\">#expenses</code> و<code dir=\"ltr\">#nextNumber</code> خاصين بها، ولهذا يعدّ مخزنان المعرّفات كلٌّ على حدة. وتُرجع <code dir=\"ltr\">list()</code> نسخة، لأن تسليم المصفوفة الحقيقية سيسمح للمُستدعين بتعديلها وتجاوز القواعد. أما <code dir=\"ltr\">total</code> فـ getter يُحسب من البيانات الحالية في كل مرة يُقرأ فيها، فلا يمكن أن يختلف أبدًا مع القائمة، حتى بعد الحذف.</p>\n","checkLabels":{"add-returns":"<code dir=\"ltr\">add</code> تُرجع المصروف الجديد مع معرّف وفئة افتراضية","list-copy":"<code dir=\"ltr\">list</code> تُرجع نسخة، فتغييرها لا يغيّر المخزن","private":"ليست للمخزن أي خصائص بيانات عامة","remove":"<code dir=\"ltr\">remove</code> تحذف المصروف صاحب ذلك المعرّف","total-getter":"<code dir=\"ltr\">total</code> هو getter يبقى محدّثًا","total-for":"<code dir=\"ltr\">totalFor</code> تجمع فئة واحدة","independent":"مخزنان يحتفظ كلٌّ منهما ببياناته وعدّاد معرّفاته المنفصل"}},"words":574,"readMinutes":3}