|
| 1 | +<html> |
| 2 | +<head> |
| 3 | + <title>Arrow Functions vs Regular Functions</title> |
| 4 | + <link href="css/style.css" rel="stylesheet"> |
| 5 | + <style> |
| 6 | + body { max-width: 800px; margin: 2em auto; padding: 0 1em; font-family: sans-serif; } |
| 7 | + section { border: 1px solid #ccc; padding: 1em; margin: 1em 0; border-radius: 6px; } |
| 8 | + button { padding: 0.5em 1em; margin: 0.25em 0.25em 0.25em 0; } |
| 9 | + pre { background: #f5f5f5; padding: 0.75em; overflow-x: auto; } |
| 10 | + .out { background: #111; color: #0f0; padding: 0.5em; min-height: 1.5em; font-family: monospace; white-space: pre-wrap; } |
| 11 | + h2 { margin-bottom: 0.25em; } |
| 12 | + .note { color: #555; font-size: 0.9em; } |
| 13 | + </style> |
| 14 | +</head> |
| 15 | +<body> |
| 16 | + |
| 17 | +<h1>Arrow Functions vs Regular Functions</h1> |
| 18 | +<p>Open the console and click the buttons. Each section highlights one behavioral difference between |
| 19 | +<code>function</code> declarations and arrow functions (<code>=></code>).</p> |
| 20 | + |
| 21 | +<!-- ========================================================= --> |
| 22 | +<section> |
| 23 | + <h2>1. <code>this</code> binding</h2> |
| 24 | + <p class="note">Regular functions get their own <code>this</code> based on how they are called. |
| 25 | + Arrow functions capture <code>this</code> from the enclosing scope.</p> |
| 26 | + <pre>const obj = { |
| 27 | + name: "Widget", |
| 28 | + regular: function () { return this; }, // `this` === obj |
| 29 | + arrow: () => this, // `this` === outer scope (window) |
| 30 | +};</pre> |
| 31 | + <button id="thisRegular">Call regular</button> |
| 32 | + <button id="thisArrow">Call arrow</button> |
| 33 | + <div class="out" id="thisOut"></div> |
| 34 | +</section> |
| 35 | + |
| 36 | +<!-- ========================================================= --> |
| 37 | +<section> |
| 38 | + <h2>2. <code>this</code> inside a callback</h2> |
| 39 | + <p class="note">A classic bug: a regular function passed as a callback loses its <code>this</code>. |
| 40 | + An arrow function inherits it from the surrounding method, so it just works.</p> |
| 41 | + <pre>class Counter { |
| 42 | + constructor() { this.count = 0; } |
| 43 | + |
| 44 | + startBroken() { |
| 45 | + setInterval(function () { this.count++; }, 500); // `this` is window, not the Counter |
| 46 | + } |
| 47 | + |
| 48 | + startFixed() { |
| 49 | + setInterval(() => { this.count++; }, 500); // `this` is the Counter |
| 50 | + } |
| 51 | +}</pre> |
| 52 | + <button id="startBroken">Start broken (regular fn)</button> |
| 53 | + <button id="startFixed">Start fixed (arrow fn)</button> |
| 54 | + <button id="stopTimers">Stop</button> |
| 55 | + <div class="out" id="counterOut"></div> |
| 56 | +</section> |
| 57 | + |
| 58 | +<!-- ========================================================= --> |
| 59 | +<section> |
| 60 | + <h2>3. <code>arguments</code> object</h2> |
| 61 | + <p class="note">Regular functions have a special <code>arguments</code> variable. Arrow functions |
| 62 | + do not — referencing it either errors or falls through to an outer scope.</p> |
| 63 | + <pre>function regular() { return arguments.length; } |
| 64 | +const arrow = () => arguments.length; // ReferenceError (in a module)</pre> |
| 65 | + <button id="argsRegular">regular(1, 2, 3)</button> |
| 66 | + <button id="argsArrow">arrow(1, 2, 3)</button> |
| 67 | + <div class="out" id="argsOut"></div> |
| 68 | +</section> |
| 69 | + |
| 70 | +<!-- ========================================================= --> |
| 71 | +<section> |
| 72 | + <h2>4. Constructors</h2> |
| 73 | + <p class="note">Regular functions can be called with <code>new</code>. Arrow functions cannot.</p> |
| 74 | + <pre>function Person(name) { this.name = name; } |
| 75 | +const PersonArrow = (name) => { this.name = name; }; |
| 76 | + |
| 77 | +new Person("Ada"); // works |
| 78 | +new PersonArrow("Ada"); // TypeError: PersonArrow is not a constructor</pre> |
| 79 | + <button id="newRegular">new Person("Ada")</button> |
| 80 | + <button id="newArrow">new PersonArrow("Ada")</button> |
| 81 | + <div class="out" id="newOut"></div> |
| 82 | +</section> |
| 83 | + |
| 84 | +<!-- ========================================================= --> |
| 85 | +<section> |
| 86 | + <h2>5. Hoisting</h2> |
| 87 | + <p class="note">Function declarations are hoisted — you can call them before the line that defines them. |
| 88 | + Arrow functions are assigned to variables, so they follow <code>const</code>/<code>let</code> rules.</p> |
| 89 | + <pre>hoisted(); // works |
| 90 | +notHoisted(); // ReferenceError |
| 91 | + |
| 92 | +function hoisted() { return "I'm up here early"; } |
| 93 | +const notHoisted = () => "I'm only available after this line";</pre> |
| 94 | + <button id="hoistBtn">Run hoisting demo</button> |
| 95 | + <div class="out" id="hoistOut"></div> |
| 96 | +</section> |
| 97 | + |
| 98 | +<script> |
| 99 | + function log(id, msg) { |
| 100 | + const el = document.getElementById(id); |
| 101 | + el.textContent += msg + "\n"; |
| 102 | + console.log(msg); |
| 103 | + } |
| 104 | + function clear(id) { document.getElementById(id).textContent = ""; } |
| 105 | + |
| 106 | + // 1. `this` binding ------------------------------------------------ |
| 107 | + const obj = { |
| 108 | + name: "Widget", |
| 109 | + regular: function () { return this; }, |
| 110 | + arrow: () => this, |
| 111 | + }; |
| 112 | + document.getElementById("thisRegular").onclick = () => { |
| 113 | + clear("thisOut"); |
| 114 | + const t = obj.regular(); |
| 115 | + log("thisOut", "regular() -> this.name = " + (t && t.name)); |
| 116 | + log("thisOut", "this === obj? " + (t === obj)); |
| 117 | + }; |
| 118 | + document.getElementById("thisArrow").onclick = () => { |
| 119 | + clear("thisOut"); |
| 120 | + const t = obj.arrow(); |
| 121 | + log("thisOut", "arrow() -> this === window? " + (t === window)); |
| 122 | + log("thisOut", "this.name = " + (t && t.name)); // undefined |
| 123 | + }; |
| 124 | + |
| 125 | + // 2. `this` in a callback ------------------------------------------ |
| 126 | + class Counter { |
| 127 | + constructor() { this.count = 0; this.timer = null; } |
| 128 | + startBroken() { |
| 129 | + this.stop(); |
| 130 | + this.timer = setInterval(function () { |
| 131 | + // `this` here is window, so this.count is undefined |
| 132 | + log("counterOut", "broken: this.count = " + this.count); |
| 133 | + }, 500); |
| 134 | + } |
| 135 | + startFixed() { |
| 136 | + this.stop(); |
| 137 | + this.count = 0; |
| 138 | + this.timer = setInterval(() => { |
| 139 | + this.count++; |
| 140 | + log("counterOut", "fixed: this.count = " + this.count); |
| 141 | + }, 500); |
| 142 | + } |
| 143 | + stop() { if (this.timer) { clearInterval(this.timer); this.timer = null; } } |
| 144 | + } |
| 145 | + const counter = new Counter(); |
| 146 | + document.getElementById("startBroken").onclick = () => { clear("counterOut"); counter.startBroken(); }; |
| 147 | + document.getElementById("startFixed").onclick = () => { clear("counterOut"); counter.startFixed(); }; |
| 148 | + document.getElementById("stopTimers").onclick = () => counter.stop(); |
| 149 | + |
| 150 | + // 3. `arguments` --------------------------------------------------- |
| 151 | + function regularArgs() { return Array.from(arguments); } |
| 152 | + const arrowArgs = () => { |
| 153 | + try { return Array.from(arguments); } |
| 154 | + catch (e) { return "Error: " + e.message; } |
| 155 | + }; |
| 156 | + document.getElementById("argsRegular").onclick = () => { |
| 157 | + clear("argsOut"); |
| 158 | + log("argsOut", "regularArgs(1, 2, 3) = " + JSON.stringify(regularArgs(1, 2, 3))); |
| 159 | + }; |
| 160 | + document.getElementById("argsArrow").onclick = () => { |
| 161 | + clear("argsOut"); |
| 162 | + const result = arrowArgs(1, 2, 3); |
| 163 | + log("argsOut", "arrowArgs(1, 2, 3) = " + JSON.stringify(result)); |
| 164 | + log("argsOut", "(Arrow functions inherit `arguments` from the enclosing scope,"); |
| 165 | + log("argsOut", " so use rest params instead: const f = (...args) => args;)"); |
| 166 | + }; |
| 167 | + |
| 168 | + // 4. Constructors -------------------------------------------------- |
| 169 | + function Person(name) { this.name = name; } |
| 170 | + const PersonArrow = (name) => { this.name = name; }; |
| 171 | + document.getElementById("newRegular").onclick = () => { |
| 172 | + clear("newOut"); |
| 173 | + const p = new Person("Ada"); |
| 174 | + log("newOut", "new Person('Ada') -> " + JSON.stringify(p)); |
| 175 | + }; |
| 176 | + document.getElementById("newArrow").onclick = () => { |
| 177 | + clear("newOut"); |
| 178 | + try { new PersonArrow("Ada"); } |
| 179 | + catch (e) { log("newOut", "TypeError: " + e.message); } |
| 180 | + }; |
| 181 | + |
| 182 | + // 5. Hoisting ------------------------------------------------------ |
| 183 | + document.getElementById("hoistBtn").onclick = () => { |
| 184 | + clear("hoistOut"); |
| 185 | + // Inside this handler we simulate top-level ordering by using eval-free tricks. |
| 186 | + // Declarations are hoisted within this function body too. |
| 187 | + log("hoistOut", "hoisted() returns: " + hoisted()); |
| 188 | + try { log("hoistOut", "notHoisted() returns: " + notHoisted()); } |
| 189 | + catch (e) { log("hoistOut", "notHoisted() threw: " + e.message); } |
| 190 | + |
| 191 | + function hoisted() { return "I'm up here early"; } |
| 192 | + const notHoisted = () => "I'm only available after this line"; |
| 193 | + }; |
| 194 | +</script> |
| 195 | + |
| 196 | +</body> |
| 197 | +</html> |
0 commit comments