{"id":"l-js-5-2","courseId":"javascript-fundamentals","lang":"en","kind":"lesson","summary":"Define classes with a constructor, methods, getters and private fields, create independent instances with new, and avoid the classic lost-this bug when passing methods as callbacks.","html":"<p>Look at what Pocket’s code does with its expenses: a variable holds the array, and a dozen functions each take that array, do something, and return a result. The data and the operations on it are separate, so nothing stops a stray line of code from pushing a malformed expense straight into the array. A <strong>class</strong> bundles data with the functions allowed to work on it, and can make the data itself unreachable from outside.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Defining a class</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=\"Run this example\"><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>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><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>Copy</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>There is a lot here, so take it one piece at a time.</p>\n<p><code dir=\"ltr\">#expenses = []</code> declares a <strong>private field</strong>. Every instance gets its own <code dir=\"ltr\">#expenses</code>, and the <code dir=\"ltr\">#</code> makes it private: only code inside the class body can read or write it. Outside, <code dir=\"ltr\">store.#expenses</code> is a syntax error. Anyone who wants to change the list has to go through <code dir=\"ltr\">add</code> and <code dir=\"ltr\">remove</code>, which guarantee every expense gets an id and a category.</p>\n<p><code dir=\"ltr\">constructor</code> is a special method that runs when you write <code dir=\"ltr\">new ExpenseStore(...)</code>. Its job is to set the new object up. Here it adds any initial expenses through <code dir=\"ltr\">add</code>, so they follow the same rules.</p>\n<p><code dir=\"ltr\">add</code>, <code dir=\"ltr\">remove</code> and <code dir=\"ltr\">list</code> are <strong>methods</strong>. Inside a method, <code dir=\"ltr\">this</code> refers to the object the method was called on: in <code dir=\"ltr\">store.add(...)</code>, <code dir=\"ltr\">this</code> is <code dir=\"ltr\">store</code>.</p>\n<p><code dir=\"ltr\">get total()</code> is a <strong>getter</strong>. You read it like a property, <code dir=\"ltr\">store.total</code>, without parentheses, but it runs code each time. A total stored in a field could go stale when an expense is removed; a getter is always computed from the current data.</p>\n<p><code dir=\"ltr\">list()</code> returns a copy, <code dir=\"ltr\">[...this.#expenses]</code>, not the array itself. Returning the real array would let callers <code dir=\"ltr\">push</code> into it and undo all the protection.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Instances: one blueprint, many objects</h2>\n<p><code dir=\"ltr\">new</code> creates a fresh object, runs the constructor with <code dir=\"ltr\">this</code> pointing at it, and returns it. Each instance has its own fields; the methods are defined once on the class and shared.</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\">The ExpenseStore class defines the methods add, remove, list and the total getter. Two instances, home and work, each have their own private expenses array, and both use the shared 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 items</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 items</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>One class, many instances with their own data</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=\"Run this example\"><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>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><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>Copy</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>If this reminds you of <code dir=\"ltr\">createBudget</code> from the closures lesson, it should. A closure and a class with private fields solve the same problem, data that only certain functions can touch. Closures are lighter for a single function; classes are clearer when several operations share the same data.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">The lost this</h2>\n<p><code dir=\"ltr\">this</code> is not fixed when you write a method. It is decided each time the function is <strong>called</strong>, by what is to the left of the dot. <code dir=\"ltr\">store.add(x)</code> calls <code dir=\"ltr\">add</code> with <code dir=\"ltr\">this</code> set to <code dir=\"ltr\">store</code>. But if you pass the method somewhere on its own, there is no dot when it is eventually called:</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=\"Run this example\"><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>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><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>Copy</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>The first call works because <code dir=\"ltr\">g</code> is to the left of the dot. <code dir=\"ltr\">detached()</code> is called with nothing to the left of a dot, so inside it <code dir=\"ltr\">this</code> is <code dir=\"ltr\">undefined</code> (class bodies always run in strict mode), and reading <code dir=\"ltr\">this.name</code> throws.</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\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Passing a method as a callback</span></p>\n<p><code dir=\"ltr\">form.addEventListener(&quot;submit&quot;, store.add)</code> and <code dir=\"ltr\">items.forEach(store.add)</code> both hit this bug. Wrap the call in an arrow function, <code dir=\"ltr\">(e) =&gt; store.add(e)</code>, so the method is called on the right object. Arrow functions do not have their own <code dir=\"ltr\">this</code>, which is exactly why they are safe here.</p>\n</aside>\n<p>Classes can also build on other classes with <code dir=\"ltr\">extends</code>, inheriting their methods and adding new ones. You will use that once, in the next lesson, to make your own error type. Beyond that, a good default for application code is to prefer small classes and plain functions over deep inheritance chains, which tend to be hard to change.</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\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Do you need a class at all?</span></p>\n<p>Pocket’s money helpers are better as plain functions: they hold no data. Reach for a class when you have state plus rules about changing it, like the store. Many excellent JavaScript codebases use few classes; knowing how to read them is non-negotiable, because browser APIs and libraries are full of them.</p>\n</aside>\n<p>Your store now refuses bad changes from outside. The next lesson is about what happens when something goes wrong anyway: errors, how to throw and catch them on purpose, and how to hunt down bugs with the browser DevTools.</p>\n","toc":[{"id":"sec-1","text":"Defining a class","level":2},{"id":"sec-2","text":"Instances: one blueprint, many objects","level":2},{"id":"sec-3","text":"The lost this","level":2}],"takeaways":["A class is a blueprint; <code dir=\"ltr\">new ClassName(...)</code> runs the constructor and returns a new instance with its own data.","Methods are shared by all instances, and inside them <code dir=\"ltr\">this</code> is the instance the method was called on.","Private fields (<code dir=\"ltr\">#expenses</code>) can only be read inside the class body, which protects an object’s data the way closures do.","A getter (<code dir=\"ltr\">get total()</code>) is read like a property but computed each time, so derived values never go stale.","Passing <code dir=\"ltr\">store.add</code> as a callback loses <code dir=\"ltr\">this</code>; wrap it in an arrow function, <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>What does this log?</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=\"Copy code\"><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>Copy</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":"Fields are created per instance, so <code dir=\"ltr\">b</code> has its own <code dir=\"ltr\">count</code> that nobody incremented."},{"text":"<code dir=\"ltr\">1 1</code>","why":"<code dir=\"ltr\">increment</code> was called twice on <code dir=\"ltr\">a</code>, and never on <code dir=\"ltr\">b</code>."},{"text":"<code dir=\"ltr\">0 0</code>","why":"<code dir=\"ltr\">this.count++</code> changes the instance it is called on, so <code dir=\"ltr\">a.count</code> goes up."},{"text":"<code dir=\"ltr\">2 0</code>","why":"Correct. Each <code dir=\"ltr\">new Counter()</code> creates a separate object with its own <code dir=\"ltr\">count</code>; the method is shared, the data is not."}],"answer":3},{"q":"<p><code dir=\"ltr\">button.addEventListener(&quot;click&quot;, store.clear);</code> makes <code dir=\"ltr\">clear</code> throw a TypeError about the private field <code dir=\"ltr\">#expenses</code> (“…from an object whose class did not declare it”) when the button is clicked. Why?</p>","options":[{"text":"Class methods cannot be used as event listeners.","why":"They can be, as long as they are called with the right <code dir=\"ltr\">this</code>. The problem is how the method is passed."},{"text":"The method was passed on its own, so when the browser calls it, <code dir=\"ltr\">this</code> is not <code dir=\"ltr\">store</code>.","why":"Correct. <code dir=\"ltr\">this</code> is decided by how a function is called, and the browser calls a listener with <code dir=\"ltr\">this</code> set to the element, here the button. Write <code dir=\"ltr\">() =&gt; store.clear()</code> so it is called on <code dir=\"ltr\">store</code>."},{"text":"<code dir=\"ltr\">store</code> was created with <code dir=\"ltr\">const</code> and cannot be changed.","why":"<code dir=\"ltr\">const</code> does not stop an object’s methods from changing its fields. The issue is the lost <code dir=\"ltr\">this</code>."}],"answer":1},{"q":"<p>An <code dir=\"ltr\">ExpenseStore</code> keeps its list in <code dir=\"ltr\">#expenses</code>. What does <code dir=\"ltr\">store.#expenses</code> written outside the class do?</p>","options":[{"text":"It is a syntax error; private fields can only be accessed inside the class body.","why":"Correct. The engine enforces privacy, so outside code has to use the public methods."},{"text":"It returns the array, because <code dir=\"ltr\">#</code> is only a naming convention.","why":"The older <code dir=\"ltr\">_expenses</code> convention was only a hint. <code dir=\"ltr\">#</code> is real privacy enforced by the language."},{"text":"It returns <code dir=\"ltr\">undefined</code>.","why":"It does not run at all. Using <code dir=\"ltr\">#expenses</code> outside the class is rejected when the code is parsed."}],"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":"A store that protects its data","prompt":"<p>Rewrite <code dir=\"ltr\">ExpenseStore</code> so its data can only change through its methods:</p>\n<ul>\n<li>Keep the expenses in a <strong>private field</strong> <code dir=\"ltr\">#expenses</code>, so instances have no public properties.</li>\n<li><code dir=\"ltr\">add({ label, amount, category })</code> creates an expense with an id (<code dir=\"ltr\">&quot;exp-1&quot;</code>, <code dir=\"ltr\">&quot;exp-2&quot;</code>… counted per store), a <code dir=\"ltr\">category</code> defaulting to <code dir=\"ltr\">&quot;other&quot;</code>, stores it and <strong>returns</strong> it.</li>\n<li><code dir=\"ltr\">remove(id)</code> removes the expense with that id.</li>\n<li><code dir=\"ltr\">list()</code> returns a <strong>copy</strong> of the expenses array.</li>\n<li><code dir=\"ltr\">total</code> is a <strong>getter</strong> returning the sum of all amounts.</li>\n<li><code dir=\"ltr\">totalFor(category)</code> returns the sum for one category (0 if none).</li>\n</ul>\n","hints":["Declare fields at the top of the class body: <code dir=\"ltr\">#expenses = [];</code> and <code dir=\"ltr\">#nextNumber = 1;</code>. Inside methods, use <code dir=\"ltr\">this.#expenses</code>.","Build the id with <code dir=\"ltr\">`exp-${this.#nextNumber++}`</code>, and destructure the parameter with a default: <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> returns <code dir=\"ltr\">[...this.#expenses]</code>."],"explanation":"<p>The private fields mean the only way to change the list is through <code dir=\"ltr\">add</code> and <code dir=\"ltr\">remove</code>, so every expense is guaranteed an id and a category, and outside code cannot slip a half-formed object in. Each <code dir=\"ltr\">new ExpenseStore()</code> gets its own <code dir=\"ltr\">#expenses</code> and <code dir=\"ltr\">#nextNumber</code>, which is why two stores count ids independently. <code dir=\"ltr\">list()</code> returns a copy, because handing out the real array would let callers mutate it and bypass the rules. <code dir=\"ltr\">total</code> is a getter computed from the current data each time it is read, so it can never disagree with the list, even after a removal.</p>\n","checkLabels":{"add-returns":"<code dir=\"ltr\">add</code> returns the new expense with an id and a default category","list-copy":"<code dir=\"ltr\">list</code> returns a copy, so changing it does not change the store","private":"The store has no public data properties","remove":"<code dir=\"ltr\">remove</code> deletes the expense with that id","total-getter":"<code dir=\"ltr\">total</code> is a getter that stays up to date","total-for":"<code dir=\"ltr\">totalFor</code> sums one category","independent":"Two stores keep separate data and id counters"}},"words":681,"readMinutes":3}