์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ” ํด๋กœ์ €์™€ ์Šค์ฝ”ํ”„ ์ฒด์ธ์„ ํ™œ์šฉํ•œ๊ณ ๊ธ‰ JavaScript ๊ฐœ๋ฐœ ๊ธฐ๋ฒ•
JavaScript ๊ณ ๊ธ‰ ๊ฐœ๋ฐœ

๐Ÿ” ํด๋กœ์ €์™€ ์Šค์ฝ”ํ”„ ์ฒด์ธ์„ ํ™œ์šฉํ•œ
๊ณ ๊ธ‰ JavaScript ๊ฐœ๋ฐœ ๊ธฐ๋ฒ•

๐Ÿš€ ์Šค์ฝ”ํ”„์˜ ๋น„๋ฐ€๋ถ€ํ„ฐ ํด๋กœ์ € ๋งˆ์Šคํ„ฐ๊นŒ์ง€ โ€” ์ง„์งœ JS ๊ฐœ๋ฐœ์ž๋กœ ๋ ˆ๋ฒจ์—…ํ•˜๋Š” ์—ฌ์ •

์•ผ, ์†”์งํžˆ ๋งํ•ด๋ณผ๊ฒŒ. ๐Ÿ˜„
JavaScript๋ฅผ ์ฒ˜์Œ ๋ฐฐ์šธ ๋•Œ ํด๋กœ์ €(Closure)๋ผ๋Š” ๋‹จ์–ด ๋“ค์œผ๋ฉด ๋ญ”๊ฐ€ ๋ฌด์„ญ์ง€ ์•Š์•˜์–ด?
"ํด๋กœ์ €๊ฐ€ ๋ญ”๋ฐ์š”?" ํ•˜๊ณ  ๋ฌผ์–ด๋ณด๋ฉด ๋Œ์•„์˜ค๋Š” ๋Œ€๋‹ต์ด ํ•ญ์ƒ ๋ญ”๊ฐ€ ์ถ”์ƒ์ ์ด๊ณ  ์ฒ ํ•™์ ์ด์ž–์•„.
"ํ•จ์ˆ˜๊ฐ€ ์ž์‹ ์ด ์„ ์–ธ๋œ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์„ ๊ธฐ์–ตํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค." โ€” ์‘? ๐Ÿค”

๊ทผ๋ฐ ์‚ฌ์‹ค ํด๋กœ์ €๋Š” ํ•œ๋ฒˆ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๋ฉด JavaScript์—์„œ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๋ฌด๊ธฐ ์ค‘ ํ•˜๋‚˜์•ผ.
๊ทธ๋ฆฌ๊ณ  ์Šค์ฝ”ํ”„ ์ฒด์ธ(Scope Chain)์ด๋ž‘ ๊ฐ™์ด ์ดํ•ดํ•˜๋ฉด, ์ฝ”๋“œ๊ฐ€ ์™œ ๊ทธ๋ ‡๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€
๋ˆˆ์— ํ›คํžˆ ๋ณด์ด๊ธฐ ์‹œ์ž‘ํ•ด. ๋งˆ์น˜ X-ray ์•ˆ๊ฒฝ์„ ์“ด ๊ฒƒ์ฒ˜๋Ÿผ! ๐Ÿ•ถ๏ธ

์ด ๊ธ€์—์„œ๋Š” ํด๋กœ์ €์™€ ์Šค์ฝ”ํ”„ ์ฒด์ธ์˜ ๊ฐœ๋…๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ,
์‹ค์ œ๋กœ ๊ณ ๊ธ‰ JavaScript ๊ฐœ๋ฐœ์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์•„์ฃผ ๊นŠ์ˆ™์ด ํŒŒ๊ณ ๋“ค ๊ฑฐ์•ผ.
๋ชจ๋“ˆ ํŒจํ„ด, ๋ฉ”๋ชจ์ด์ œ์ด์…˜, ์ปค๋ง, ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ตœ์ ํ™”๊นŒ์ง€ โ€” ์ „๋ถ€ ๋‹ค ๋‹ค๋ฃฐ ๊ฑฐ๋‹ˆ๊นŒ ๊ธฐ๋Œ€ํ•ด! ๐ŸŽฏ


1

์Šค์ฝ”ํ”„(Scope)๋ž€ ๋ญ”๋ฐ? โ€” ๋ณ€์ˆ˜์˜ '์œ ํšจ ๋ฒ”์œ„' ์ด์•ผ๊ธฐ

ํด๋กœ์ €๋ฅผ ์ดํ•ดํ•˜๋ ค๋ฉด ๋จผ์ € ์Šค์ฝ”ํ”„(Scope)๋ฅผ ์ œ๋Œ€๋กœ ์•Œ์•„์•ผ ํ•ด.
์Šค์ฝ”ํ”„๋Š” ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด "๋ณ€์ˆ˜๊ฐ€ ์‚ด ์ˆ˜ ์žˆ๋Š” ์ง‘"์ด์•ผ. ๐Ÿ 
์–ด๋–ค ๋ณ€์ˆ˜๊ฐ€ ์–ด๋””์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ณ , ์–ด๋””์„œ๋Š” ์ ‘๊ทผ ๋ถˆ๊ฐ€๋Šฅํ•œ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๊ทœ์น™์ด์ง€.

JavaScript์—๋Š” ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€ ์Šค์ฝ”ํ”„๊ฐ€ ์žˆ์–ด:

JavaScript ์Šค์ฝ”ํ”„์˜ ์„ธ ๊ฐ€์ง€ ์ง‘ ๐Ÿ  ๐ŸŒ ์ „์—ญ ์Šค์ฝ”ํ”„ (Global Scope) var globalVar = "๋‚˜๋Š” ์ „์—ญ!" ์–ด๋””์„œ๋“  ์ ‘๊ทผ ๊ฐ€๋Šฅ ํ”„๋กœ๊ทธ๋žจ ์ „์ฒด์—์„œ ์‚ด์•„์žˆ์Œ ๐ŸŒ โš ๏ธ ๋‚จ์šฉํ•˜๋ฉด ์œ„ํ—˜! ๋ณ€์ˆ˜ ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ ๋†’์Œ โš™๏ธ ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ (Function Scope) function myFunc() { var x } ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ var ํ‚ค์›Œ๋“œ๊ฐ€ ์ด ์Šค์ฝ”ํ”„ ์‚ฌ์šฉ ๐Ÿ”ง โœ… ํด๋กœ์ €์˜ ํ•ต์‹ฌ ๋ฌด๋Œ€! ์™ธ๋ถ€์—์„œ ์ ‘๊ทผ ๋ถˆ๊ฐ€ ๐Ÿ“ฆ ๋ธ”๋ก ์Šค์ฝ”ํ”„ (Block Scope) { let x = 1; const y = 2; } { } ๋ธ”๋ก ๋‚ด๋ถ€์—์„œ๋งŒ ์œ ํšจ let, const๊ฐ€ ์ด ์Šค์ฝ”ํ”„ ์‚ฌ์šฉ ๐Ÿ“ฆ โœ… ES6+์—์„œ ๊ถŒ์žฅ! if, for, while ๋ธ”๋ก ํฌํ•จ โ†’ โ†’ ES6 ์ดํ›„: let/const๋กœ ๋ธ”๋ก ์Šค์ฝ”ํ”„ ์‚ฌ์šฉ ๊ถŒ์žฅ ๐ŸŽฏ

์ž, ์ด์ œ ๊ฐ๊ฐ ์ข€ ๋” ์ž์„ธํžˆ ์‚ดํŽด๋ณผ๊ฒŒ.
์ฝ”๋“œ๋กœ ์ง์ ‘ ๋ณด๋Š” ๊ฒŒ ์ œ์ผ ๋น ๋ฅด์ž–์•„? ๐Ÿ˜Ž

// ๐ŸŒ ์ „์—ญ ์Šค์ฝ”ํ”„ ์˜ˆ์‹œ
var globalMessage = "๋‚˜๋Š” ์–ด๋””์„œ๋“  ๋ณด์—ฌ!";

function showMessage() {
    console.log(globalMessage); // โœ… ์ ‘๊ทผ ๊ฐ€๋Šฅ
}

// โš™๏ธ ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ ์˜ˆ์‹œ
function outerFunction() {
    var functionScoped = "๋‚˜๋Š” ํ•จ์ˆ˜ ์•ˆ์—์„œ๋งŒ ์‚ด์•„!";
    console.log(functionScoped); // โœ… ์ ‘๊ทผ ๊ฐ€๋Šฅ
}
// console.log(functionScoped); // โŒ ReferenceError!

// ๐Ÿ“ฆ ๋ธ”๋ก ์Šค์ฝ”ํ”„ ์˜ˆ์‹œ (ES6+)
{
    let blockScoped = "๋‚˜๋Š” ๋ธ”๋ก ์•ˆ์—์„œ๋งŒ!";
    const alsoBlock = "๋‚˜๋„ ๋ธ”๋ก ์•ˆ์—์„œ๋งŒ!";
    console.log(blockScoped); // โœ… ์ ‘๊ทผ ๊ฐ€๋Šฅ
}
// console.log(blockScoped); // โŒ ReferenceError!

// โš ๏ธ var์˜ ํ•จ์ • โ€” ๋ธ”๋ก์„ ๋ฌด์‹œํ•ด๋ฒ„๋ฆผ!
{
    var sneaky = "๋‚˜๋Š” ๋ธ”๋ก์„ ํƒˆ์ถœํ•ด!";
}
console.log(sneaky); // โœ… "๋‚˜๋Š” ๋ธ”๋ก์„ ํƒˆ์ถœํ•ด!" โ€” var๋Š” ๋ธ”๋ก ์Šค์ฝ”ํ”„ ๋ฌด์‹œ!
โš ๏ธ var์˜ ํ•จ์ •์„ ์กฐ์‹ฌํ•ด! var๋Š” ๋ธ”๋ก ์Šค์ฝ”ํ”„๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„๋งŒ ๋”ฐ๋ผ๊ฐ€. ๊ทธ๋ž˜์„œ if๋ฌธ์ด๋‚˜ for๋ฌธ ์•ˆ์—์„œ ์„ ์–ธํ•œ var๊ฐ€ ๋ฐ–์—์„œ๋„ ์ ‘๊ทผ๋˜๋Š” ํ™ฉ๋‹นํ•œ ์ƒํ™ฉ์ด ์ƒ๊ฒจ. ์ด๊ฒŒ ์ˆ˜๋งŽ์€ ๋ฒ„๊ทธ์˜ ์›์ธ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ˜„๋Œ€ JavaScript์—์„œ๋Š” let๊ณผ const๋ฅผ ์“ฐ๋Š” ๊ฒŒ ์ •์„์ด์•ผ!

2

๋ ‰์‹œ์ปฌ ์Šค์ฝ”ํ”„(Lexical Scope) โ€” ํด๋กœ์ €์˜ ์ง„์งœ ๋น„๋ฐ€

์ž, ์ด์ œ ํด๋กœ์ €๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•œ ํ•ต์‹ฌ ๊ฐœ๋…์ด ๋‚˜์™”์–ด. ๋ฐ”๋กœ ๋ ‰์‹œ์ปฌ ์Šค์ฝ”ํ”„(Lexical Scope)์•ผ.
"๋ ‰์‹œ์ปฌ"์ด๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ์ข€ ์–ด์ƒ‰ํ•˜์ง€? ์ด๊ฑด "์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์‹œ์ "์„ ์˜๋ฏธํ•ด.

JavaScript๋Š” ๋ ‰์‹œ์ปฌ ์Šค์ฝ”ํ•‘(Lexical Scoping)์„ ๋”ฐ๋ฅด๋Š”๋ฐ,
์ด ๋ง์€ "ํ•จ์ˆ˜๊ฐ€ ์–ด๋””์„œ ํ˜ธ์ถœ๋˜๋А๋ƒ"๊ฐ€ ์•„๋‹ˆ๋ผ "์–ด๋””์„œ ์ •์˜๋˜์—ˆ๋А๋ƒ"์— ๋”ฐ๋ผ ์Šค์ฝ”ํ”„๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค๋Š” ๊ฑฐ์•ผ! ๐ŸŽฏ

๐Ÿ”‘ ๋ ‰์‹œ์ปฌ ์Šค์ฝ”ํ”„์˜ ํ•ต์‹ฌ ๊ทœ์น™ ํ•จ์ˆ˜๋Š” ์ž์‹ ์ด ์„ ์–ธ๋œ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์Šค์ฝ”ํ”„๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
ํ˜ธ์ถœ๋˜๋Š” ์œ„์น˜๋Š” ์Šค์ฝ”ํ”„์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค!
// ๋ ‰์‹œ์ปฌ ์Šค์ฝ”ํ”„ ์˜ˆ์‹œ
let name = "์ „์—ญ ์ด๋ฆ„";

function outer() {
    let name = "outer์˜ ์ด๋ฆ„";
    
    function inner() {
        // inner๋Š” outer ์•ˆ์—์„œ '์ •์˜'๋˜์—ˆ์œผ๋ฏ€๋กœ
        // outer์˜ name์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด!
        console.log(name); // "outer์˜ ์ด๋ฆ„"
    }
    
    return inner;
}

const myFunc = outer();
myFunc(); // "outer์˜ ์ด๋ฆ„" ์ถœ๋ ฅ

// ์™œ? inner๊ฐ€ outer ์•ˆ์—์„œ '์ •์˜'๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ!
// myFunc()๊ฐ€ ์–ด๋””์„œ ํ˜ธ์ถœ๋˜๋“  ์ƒ๊ด€์—†์–ด.

์ด๊ฒŒ ๋ฐ”๋กœ ํด๋กœ์ €์˜ ๊ธฐ๋ฐ˜์ด์•ผ! ๐Ÿ—๏ธ
inner ํ•จ์ˆ˜๋Š” outer ํ•จ์ˆ˜ ์•ˆ์—์„œ ์ •์˜๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—,
outer์˜ ๋ณ€์ˆ˜ name์„ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด.
์‹ฌ์ง€์–ด outer ํ•จ์ˆ˜๊ฐ€ ์ด๋ฏธ ์‹คํ–‰์„ ๋งˆ์ณค๋Š”๋ฐ๋„ ๋ง์ด์•ผ!

์ด๊ฒŒ ๋ฐ”๋กœ ํด๋กœ์ €์˜ ๋งˆ๋ฒ•์ด์•ผ. ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ํด๋กœ์ €๋ฅผ ํŒŒํ—ค์ณ๋ณด์ž! ๐Ÿ”


3

์Šค์ฝ”ํ”„ ์ฒด์ธ(Scope Chain) โ€” ๋ณ€์ˆ˜๋ฅผ ์ฐพ์•„ ๋– ๋‚˜๋Š” ์—ฌํ–‰ ๐Ÿ—บ๏ธ

์Šค์ฝ”ํ”„ ์ฒด์ธ์€ JavaScript ์—”์ง„์ด ๋ณ€์ˆ˜๋ฅผ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์ด์•ผ.
ํ•จ์ˆ˜ ์•ˆ์—์„œ ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐํ•˜๋ฉด, JS ์—”์ง„์€ ์ด๋ ‡๊ฒŒ ์ƒ๊ฐํ•ด:

"์ด ๋ณ€์ˆ˜๊ฐ€ ํ˜„์žฌ ์Šค์ฝ”ํ”„์— ์žˆ๋‚˜? ์—†์œผ๋ฉด ๋ฐ”๊นฅ ์Šค์ฝ”ํ”„๋ฅผ ๋ด. ๊ฑฐ๊ธฐ๋„ ์—†์œผ๋ฉด ๋” ๋ฐ”๊นฅ์„ ๋ด. ์ „์—ญ๊นŒ์ง€ ๊ฐ€๋„ ์—†์œผ๋ฉด ์—๋Ÿฌ!"

์ด๋ ‡๊ฒŒ ์•ˆ์ชฝ์—์„œ ๋ฐ”๊นฅ์ชฝ์œผ๋กœ ๋ณ€์ˆ˜๋ฅผ ์ฐพ์•„๊ฐ€๋Š” ์—ฐ๊ฒฐ ๊ณ ๋ฆฌ๊ฐ€ ๋ฐ”๋กœ ์Šค์ฝ”ํ”„ ์ฒด์ธ์ด์•ผ! โ›“๏ธ

์Šค์ฝ”ํ”„ ์ฒด์ธ โ€” ๋ณ€์ˆ˜ ํƒ์ƒ‰ ์—ฌํ–‰ ๐Ÿ—บ๏ธ ๐ŸŒ ์ „์—ญ ์Šค์ฝ”ํ”„ globalVar = "์ „์—ญ๋ณ€์ˆ˜" โš™๏ธ outer ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ outerVar = "outer๋ณ€์ˆ˜" ๐Ÿ“ฆ inner ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„ innerVar = "inner๋ณ€์ˆ˜" console.log(outerVar) ์‹คํ–‰ ์‹œ... 1๏ธโƒฃ inner ์Šค์ฝ”ํ”„ ํƒ์ƒ‰ โ†’ ์—†์Œ! ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€! 2๏ธโƒฃ outer ์Šค์ฝ”ํ”„ ํƒ์ƒ‰ โ†’ โœ… ๋ฐœ๊ฒฌ! "outer๋ณ€์ˆ˜" ๋ฐ˜ํ™˜! ๋งŒ์•ฝ outer์—๋„ ์—†์—ˆ๋‹ค๋ฉด? โ†’ ์ „์—ญ ์Šค์ฝ”ํ”„๊นŒ์ง€ ํƒ์ƒ‰ โ†’ ๊ฑฐ๊ธฐ๋„ ์—†์œผ๋ฉด ReferenceError! inner โ†’ outer โ†’ ์ „์—ญ โ†’ โŒ (์—๋Ÿฌ)
// ์Šค์ฝ”ํ”„ ์ฒด์ธ ์‹ค์ œ ์˜ˆ์‹œ
const globalVar = "๋‚˜๋Š” ์ „์—ญ!";

function outer() {
    const outerVar = "๋‚˜๋Š” outer!";
    
    function middle() {
        const middleVar = "๋‚˜๋Š” middle!";
        
        function inner() {
            // inner ์Šค์ฝ”ํ”„์—์„œ ๋ณ€์ˆ˜ ํƒ์ƒ‰ ์ˆœ์„œ:
            // 1. inner ์Šค์ฝ”ํ”„ โ†’ ์—†์Œ
            // 2. middle ์Šค์ฝ”ํ”„ โ†’ ์—†์Œ  
            // 3. outer ์Šค์ฝ”ํ”„ โ†’ ์—†์Œ
            // 4. ์ „์—ญ ์Šค์ฝ”ํ”„ โ†’ โœ… ๋ฐœ๊ฒฌ!
            console.log(globalVar);  // "๋‚˜๋Š” ์ „์—ญ!"
            
            // middle ์Šค์ฝ”ํ”„์—์„œ ๋ฐœ๊ฒฌ!
            console.log(middleVar);  // "๋‚˜๋Š” middle!"
            
            // outer ์Šค์ฝ”ํ”„์—์„œ ๋ฐœ๊ฒฌ!
            console.log(outerVar);   // "๋‚˜๋Š” outer!"
        }
        
        inner();
    }
    
    middle();
}

outer();
๐Ÿ’ก ์Šค์ฝ”ํ”„ ์ฒด์ธ ์„ฑ๋Šฅ ํŒ! ์Šค์ฝ”ํ”„ ์ฒด์ธ์„ ๋”ฐ๋ผ ๋ณ€์ˆ˜๋ฅผ ์ฐพ์„์ˆ˜๋ก ์‹œ๊ฐ„์ด ๊ฑธ๋ ค. ๊ทธ๋ž˜์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์ „์—ญ ๋ณ€์ˆ˜๋Š” ์ง€์—ญ ๋ณ€์ˆ˜์— ์บ์‹ฑํ•ด๋‘๋ฉด ์„ฑ๋Šฅ์ด ์ข‹์•„์ ธ!
const localDoc = document; ์ด๋Ÿฐ ์‹์œผ๋กœ ์ž์ฃผ ์“ฐ๋Š” ์ „์—ญ ๊ฐ์ฒด๋ฅผ ์ง€์—ญ ๋ณ€์ˆ˜์— ๋‹ด์•„๋‘๋Š” ๊ฑฐ์•ผ.

4

ํด๋กœ์ €(Closure) โ€” ๋“œ๋””์–ด ๋ณธ๋ก ! ๐ŸŽญ

์ž, ์ด์ œ ์ง„์งœ ์ฃผ์ธ๊ณต ๋“ฑ์žฅ! ๐ŸŒŸ
ํด๋กœ์ €๋ฅผ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ •์˜ํ•˜๋ฉด:

๐Ÿ” ํด๋กœ์ €(Closure)๋ž€? ๋‚ด๋ถ€ ํ•จ์ˆ˜๊ฐ€ ์™ธ๋ถ€ ํ•จ์ˆ˜์˜ ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ.
์™ธ๋ถ€ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰์„ ๋งˆ์นœ ํ›„์—๋„, ๋‚ด๋ถ€ ํ•จ์ˆ˜๋Š” ์™ธ๋ถ€ ํ•จ์ˆ˜์˜ ๋ณ€์ˆ˜๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด!

์ด๊ฒŒ ์™œ ์‹ ๊ธฐํ•˜๋ƒ๊ณ ? ๐Ÿค”
์ผ๋ฐ˜์ ์œผ๋กœ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰์„ ๋งˆ์น˜๋ฉด ๊ทธ ํ•จ์ˆ˜์˜ ์ง€์—ญ ๋ณ€์ˆ˜๋“ค์€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์‚ฌ๋ผ์ ธ์•ผ ํ•ด.
๊ทผ๋ฐ ํด๋กœ์ €๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ๋ณ€์ˆ˜๋“ค์ด ์‚ด์•„๋‚จ์•„! ๋‚ด๋ถ€ ํ•จ์ˆ˜๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—.

์ด๊ฑธ JavaScript ์—”์ง„์ด ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜(Garbage Collection)์—์„œ ์ œ์™ธ์‹œ์ผœ์ฃผ๋Š” ๊ฑฐ์•ผ.
"์•„, ์ด ๋ณ€์ˆ˜๋Š” ์•„์ง ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์“ฐ๊ณ  ์žˆ๊ตฌ๋‚˜. ์ง€์šฐ๋ฉด ์•ˆ ๋˜๊ฒ ๋‹ค!" ํ•˜๊ณ  ๋ง์ด์•ผ. ๐Ÿ—‘๏ธโŒ

// ํด๋กœ์ €์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์‹œ
function makeCounter() {
    let count = 0; // ์ด ๋ณ€์ˆ˜๊ฐ€ ํด๋กœ์ €์— ์˜ํ•ด '์‚ด์•„๋‚จ์•„'!
    
    return function() {
        count++;
        return count;
    };
}

const counter = makeCounter();
// makeCounter()๋Š” ์ด๋ฏธ ์‹คํ–‰์„ ๋งˆ์ณค์–ด!
// ๊ทผ๋ฐ count ๋ณ€์ˆ˜๋Š” ์•„์ง ์‚ด์•„์žˆ์–ด!

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// ์ƒˆ๋กœ์šด ์นด์šดํ„ฐ๋ฅผ ๋งŒ๋“ค๋ฉด ๋…๋ฆฝ์ ์ธ count๋ฅผ ๊ฐ€์ ธ!
const counter2 = makeCounter();
console.log(counter2()); // 1 (counter์™€ ๋…๋ฆฝ์ !)
console.log(counter());  // 4 (counter๋Š” ๊ณ„์† ์ด์–ด์„œ!)

๋ดค์–ด? ๐ŸŽ‰
makeCounter()๊ฐ€ ์‹คํ–‰์„ ๋งˆ์ณค๋Š”๋ฐ๋„ count๊ฐ€ ์‚ด์•„์žˆ์–ด!
๊ทธ๋ฆฌ๊ณ  counter์™€ counter2๋Š” ๊ฐ๊ฐ ๋…๋ฆฝ์ ์ธ count๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด.
์ด๊ฒŒ ํด๋กœ์ €์˜ ํ•ต์‹ฌ์ด์•ผ!

์ด์ œ ์ด๊ฑธ ํ™œ์šฉํ•œ ๊ณ ๊ธ‰ ํŒจํ„ด๋“ค์„ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์ž. ์ง„์งœ ์žฌ๋ฏธ์žˆ๋Š” ๋ถ€๋ถ„์ด ์‹œ์ž‘๋ผ! ๐Ÿš€


5

์‹คํ–‰ ์ปจํ…์ŠคํŠธ์™€ ํ™˜๊ฒฝ ๋ ˆ์ฝ”๋“œ โ€” ํด๋กœ์ €์˜ ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ ๐Ÿ”ฌ

ํด๋กœ์ €๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋” ๊นŠ์ด ์ดํ•ดํ•˜๋ ค๋ฉด
์‹คํ–‰ ์ปจํ…์ŠคํŠธ(Execution Context)์™€ ํ™˜๊ฒฝ ๋ ˆ์ฝ”๋“œ(Environment Record)๋ฅผ ์•Œ์•„์•ผ ํ•ด.
์ด๊ฒŒ JavaScript ์—”์ง„์ด ์‹ค์ œ๋กœ ํด๋กœ์ €๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹์ด๊ฑฐ๋“ ! ๐Ÿ”ง

์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋ž€?

JavaScript ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ๋งŒ๋“ค์–ด์ง€๋Š” ์‹คํ–‰ ํ™˜๊ฒฝ์ด์•ผ.
ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ์ฝœ ์Šคํƒ(Call Stack)์— ์Œ“์—ฌ.
๊ฐ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋Š” ๋‹ค์Œ์„ ํฌํ•จํ•ด:

1. ๋ณ€์ˆ˜ ํ™˜๊ฒฝ(Variable Environment) โ€” var๋กœ ์„ ์–ธ๋œ ๋ณ€์ˆ˜๋“ค
2. ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ(Lexical Environment) โ€” let, const, ํ•จ์ˆ˜ ์„ ์–ธ๋“ค
3. this ๋ฐ”์ธ๋”ฉ โ€” ํ˜„์žฌ ์ปจํ…์ŠคํŠธ์˜ this ๊ฐ’

// ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ํ๋ฆ„ ์ดํ•ดํ•˜๊ธฐ
function outer() {
    // outer ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
    // ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ: { outerVar: "outer", inner: [Function] }
    // ์™ธ๋ถ€ ์ฐธ์กฐ: ์ „์—ญ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ
    
    let outerVar = "outer";
    
    function inner() {
        // inner ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
        // ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ: { innerVar: "inner" }
        // ์™ธ๋ถ€ ์ฐธ์กฐ: outer์˜ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ โ† ์ด๊ฒŒ ํด๋กœ์ €!
        
        let innerVar = "inner";
        console.log(outerVar); // outer์˜ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์—์„œ ์ฐพ์Œ
    }
    
    return inner; // inner ํ•จ์ˆ˜ ๋ฐ˜ํ™˜
    // outer ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋Š” ์ฝœ ์Šคํƒ์—์„œ ์ œ๊ฑฐ๋จ
    // ํ•˜์ง€๋งŒ! inner๊ฐ€ outer์˜ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์„ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ
    // outer์˜ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์€ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€๋จ!
}

const closureFunc = outer();
closureFunc(); // "outer" โ€” outer์˜ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์—์„œ outerVar๋ฅผ ์ฐพ์•„๋ƒ„!
ํด๋กœ์ €์˜ ๋ฉ”๋ชจ๋ฆฌ ๊ตฌ์กฐ ๐Ÿ”ฌ ๐Ÿ“š ์ฝœ ์Šคํƒ ์ „์—ญ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ closureFunc = inner outer() ์ปจํ…์ŠคํŠธ ์‹คํ–‰ ์™„๋ฃŒ ํ›„ ์ œ๊ฑฐ๋จ โ†‘ ์Šคํƒ์—์„œ ์ œ๊ฑฐ ํ•˜์ง€๋งŒ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์€ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์•„์žˆ์–ด! ๐Ÿ—„๏ธ ํž™ ๋ฉ”๋ชจ๋ฆฌ (Heap Memory) inner ํ•จ์ˆ˜ ๊ฐ์ฒด ์ฝ”๋“œ: function inner() {...} [[Environment]]: โ†’ outer์˜ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ ์ฐธ์กฐ โฌ†๏ธ ์ด๊ฒŒ ํด๋กœ์ €! outer์˜ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ ํ™˜๊ฒฝ ๋ ˆ์ฝ”๋“œ: outerVar: "outer" inner: [Function] outer() ์ข…๋ฃŒ ํ›„์—๋„ ์œ ์ง€! ํด๋กœ์ € ๋™์ž‘ ํ๋ฆ„ 1๏ธโƒฃ outer() ํ˜ธ์ถœ โ†’ outer ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ, ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ ์ƒ์„ฑ 2๏ธโƒฃ inner ํ•จ์ˆ˜ ์ƒ์„ฑ โ†’ [[Environment]]์— outer ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ ์ €์žฅ 3๏ธโƒฃ outer() ์ข…๋ฃŒ โ†’ ์ฝœ ์Šคํƒ์—์„œ ์ œ๊ฑฐ, ํ•˜์ง€๋งŒ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์€ ์œ ์ง€! 4๏ธโƒฃ closureFunc() ํ˜ธ์ถœ โ†’ inner ์‹คํ–‰, [[Environment]]๋กœ outerVar ์ ‘๊ทผ! โœ… ์ด๊ฒƒ์ด ํด๋กœ์ €! ์™ธ๋ถ€ ํ•จ์ˆ˜ ์ข…๋ฃŒ ํ›„์—๋„ ๋ณ€์ˆ˜ ์ ‘๊ทผ ๊ฐ€๋Šฅ!

6

ํด๋กœ์ € ํ™œ์šฉ ํŒจํ„ด โ‘  โ€” ๋ฐ์ดํ„ฐ ์€๋‹‰๊ณผ ์บก์Аํ™” ๐Ÿ”’

ํด๋กœ์ €์˜ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ํ™œ์šฉ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ๋ฐ์ดํ„ฐ ์€๋‹‰(Data Hiding)์ด์•ผ.
JavaScript์—๋Š” ์›๋ž˜ private ํ‚ค์›Œ๋“œ๊ฐ€ ์—†์—ˆ์–ด (ES2022์— # ๋ฌธ๋ฒ•์ด ์ƒ๊ฒผ์ง€๋งŒ).
๊ทธ๋ž˜์„œ ํด๋กœ์ €๋ฅผ ์ด์šฉํ•ด์„œ ์™ธ๋ถ€์—์„œ ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ์ง€! ๐Ÿ›ก๏ธ

// ํด๋กœ์ €๋ฅผ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ ์€๋‹‰
function createBankAccount(initialBalance) {
    // ์ด ๋ณ€์ˆ˜๋Š” ์™ธ๋ถ€์—์„œ ์ง์ ‘ ์ ‘๊ทผ ๋ถˆ๊ฐ€!
    let balance = initialBalance;
    let transactionHistory = [];
    
    return {
        // ์ž”์•ก ์กฐํšŒ (์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅ)
        getBalance() {
            return balance;
        },
        
        // ์ž…๊ธˆ
        deposit(amount) {
            if (amount <= 0) {
                throw new Error("์ž…๊ธˆ์•ก์€ 0๋ณด๋‹ค ์ปค์•ผ ํ•ฉ๋‹ˆ๋‹ค!");
            }
            balance += amount;
            transactionHistory.push({
                type: "์ž…๊ธˆ",
                amount,
                balance,
                date: new Date().toISOString()
            });
            return `์ž…๊ธˆ ์™„๋ฃŒ! ํ˜„์žฌ ์ž”์•ก: ${balance}์›`;
        },
        
        // ์ถœ๊ธˆ
        withdraw(amount) {
            if (amount > balance) {
                throw new Error("์ž”์•ก์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค!");
            }
            balance -= amount;
            transactionHistory.push({
                type: "์ถœ๊ธˆ",
                amount,
                balance,
                date: new Date().toISOString()
            });
            return `์ถœ๊ธˆ ์™„๋ฃŒ! ํ˜„์žฌ ์ž”์•ก: ${balance}์›`;
        },
        
        // ๊ฑฐ๋ž˜ ๋‚ด์—ญ ์กฐํšŒ
        getHistory() {
            return [...transactionHistory]; // ๋ณต์‚ฌ๋ณธ ๋ฐ˜ํ™˜ (์›๋ณธ ๋ณดํ˜ธ!)
        }
    };
}

const myAccount = createBankAccount(10000);

console.log(myAccount.getBalance()); // 10000
console.log(myAccount.deposit(5000)); // "์ž…๊ธˆ ์™„๋ฃŒ! ํ˜„์žฌ ์ž”์•ก: 15000์›"
console.log(myAccount.withdraw(3000)); // "์ถœ๊ธˆ ์™„๋ฃŒ! ํ˜„์žฌ ์ž”์•ก: 12000์›"

// balance์— ์ง์ ‘ ์ ‘๊ทผ ๋ถˆ๊ฐ€!
console.log(myAccount.balance); // undefined โ€” ๋ณดํ˜ธ๋จ! ๐Ÿ”’
// myAccount.balance = 999999; // ์ด๋ž˜๋„ ์‹ค์ œ balance๋Š” ๋ณ€ํ•˜์ง€ ์•Š์•„!
๐Ÿ’ก ์ด๊ฒŒ ์™œ ์ค‘์š”ํ•ด? ์€ํ–‰ ๊ณ„์ขŒ ์˜ˆ์‹œ์ฒ˜๋Ÿผ, ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์™ธ๋ถ€์—์„œ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋ง‰์„ ์ˆ˜ ์žˆ์–ด.
balance๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ deposit()์ด๋‚˜ withdraw()๋ฅผ ํ†ตํ•ด์•ผ ํ•˜๋‹ˆ๊นŒ,
์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์ด ํ•ญ์ƒ ์‹คํ–‰๋˜๋Š” ๊ฑฐ์•ผ. ์ด๊ฒŒ ๋ฐ”๋กœ ์บก์Аํ™”(Encapsulation)์˜ ํ•ต์‹ฌ์ด์•ผ!

์ด ํŒจํ„ด์„ ๋” ๋ฐœ์ „์‹œํ‚ค๋ฉด ๋ชจ๋“ˆ ํŒจํ„ด(Module Pattern)์ด ๋ผ. ํ•œ๋ฒˆ ๋ณด์ž! ๐Ÿ‘‡

// ๋ชจ๋“ˆ ํŒจํ„ด (IIFE + ํด๋กœ์ €)
const UserModule = (function() {
    // private ๋ณ€์ˆ˜๋“ค
    let users = [];
    let nextId = 1;
    
    // private ํ•จ์ˆ˜
    function validateUser(user) {
        return user.name && user.email && user.email.includes('@');
    }
    
    // public API ๋ฐ˜ํ™˜
    return {
        addUser(name, email) {
            const user = { id: nextId++, name, email };
            if (!validateUser(user)) {
                throw new Error("์œ ํšจํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž ์ •๋ณด์ž…๋‹ˆ๋‹ค!");
            }
            users.push(user);
            return user;
        },
        
        getUser(id) {
            return users.find(u => u.id === id) || null;
        },
        
        getAllUsers() {
            return [...users]; // ๋ณต์‚ฌ๋ณธ ๋ฐ˜ํ™˜
        },
        
        getUserCount() {
            return users.length;
        }
    };
})(); // IIFE: ์ฆ‰์‹œ ์‹คํ–‰ ํ•จ์ˆ˜ ํ‘œํ˜„์‹

UserModule.addUser("๊น€์ฒ ์ˆ˜", "kim@example.com");
UserModule.addUser("์ด์˜ํฌ", "lee@example.com");

console.log(UserModule.getUserCount()); // 2
console.log(UserModule.getUser(1)); // { id: 1, name: "๊น€์ฒ ์ˆ˜", ... }

// users ๋ฐฐ์—ด์— ์ง์ ‘ ์ ‘๊ทผ ๋ถˆ๊ฐ€!
console.log(UserModule.users); // undefined ๐Ÿ”’

7

ํด๋กœ์ € ํ™œ์šฉ ํŒจํ„ด โ‘ก โ€” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜์™€ ๋ถ€๋ถ„ ์ ์šฉ ๐Ÿญ

ํด๋กœ์ €๋ฅผ ์ด์šฉํ•˜๋ฉด ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜(Factory Function)๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.
ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋Š” ํŠน์ • ์„ค์ •์„ ๊ธฐ์–ตํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š” ํ•จ์ˆ˜์•ผ.
๋งˆ์น˜ ๊ณต์žฅ์—์„œ ๊ฐ™์€ ํ‹€๋กœ ๋‹ค์–‘ํ•œ ์ œํ’ˆ์„ ์ฐ์–ด๋‚ด๋Š” ๊ฒƒ์ฒ˜๋Ÿผ! ๐Ÿญ

// ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ ์˜ˆ์‹œ 1: ์Šน์ˆ˜ ์ƒ์„ฑ๊ธฐ
function createMultiplier(multiplier) {
    // multiplier๋ฅผ ํด๋กœ์ €๋กœ ๊ธฐ์–ต!
    return function(number) {
        return number * multiplier;
    };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const tenTimes = createMultiplier(10);

console.log(double(5));    // 10
console.log(triple(5));    // 15
console.log(tenTimes(5));  // 50

// ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ ์˜ˆ์‹œ 2: API ์š”์ฒญ ์ƒ์„ฑ๊ธฐ
function createApiClient(baseUrl, apiKey) {
    // baseUrl๊ณผ apiKey๋ฅผ ํด๋กœ์ €๋กœ ๊ธฐ์–ต!
    const headers = {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
    };
    
    return {
        async get(endpoint) {
            const response = await fetch(`${baseUrl}${endpoint}`, {
                method: 'GET',
                headers
            });
            return response.json();
        },
        
        async post(endpoint, data) {
            const response = await fetch(`${baseUrl}${endpoint}`, {
                method: 'POST',
                headers,
                body: JSON.stringify(data)
            });
            return response.json();
        }
    };
}

// ๊ฐ ์„œ๋น„์Šค๋ณ„ ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
const userApi = createApiClient('https://api.example.com/users', 'user-api-key-123');
const productApi = createApiClient('https://api.example.com/products', 'product-api-key-456');

// ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ!
// userApi.get('/profile');
// productApi.post('/create', { name: '์ƒˆ ์ƒํ’ˆ' });

์ด์ œ ๋ถ€๋ถ„ ์ ์šฉ(Partial Application)๋„ ์‚ดํŽด๋ณด์ž!
๋ถ€๋ถ„ ์ ์šฉ์€ ํ•จ์ˆ˜์˜ ์ผ๋ถ€ ์ธ์ž๋ฅผ ๋ฏธ๋ฆฌ ๊ณ ์ •ํ•ด๋‘๋Š” ๊ธฐ๋ฒ•์ด์•ผ. ๐ŸŽฏ

// ๋ถ€๋ถ„ ์ ์šฉ (Partial Application)
function partial(fn, ...presetArgs) {
    return function(...laterArgs) {
        return fn(...presetArgs, ...laterArgs);
    };
}

// ์˜ˆ์‹œ: ๋กœ๊ทธ ํ•จ์ˆ˜
function log(level, timestamp, message) {
    console.log(`[${level}] ${timestamp}: ${message}`);
}

// ๋ ˆ๋ฒจ์„ ๋ฏธ๋ฆฌ ๊ณ ์ •ํ•œ ๋กœ๊ฑฐ ์ƒ์„ฑ
const errorLog = partial(log, 'ERROR');
const infoLog = partial(log, 'INFO');
const warnLog = partial(log, 'WARN');

// ํƒ€์ž„์Šคํƒฌํ”„๋„ ๊ณ ์ •ํ•œ ๋กœ๊ฑฐ
const now = new Date().toISOString();
const errorLogNow = partial(log, 'ERROR', now);

errorLog('2024-01-01', '์„œ๋ฒ„ ์˜ค๋ฅ˜ ๋ฐœ์ƒ!');
// [ERROR] 2024-01-01: ์„œ๋ฒ„ ์˜ค๋ฅ˜ ๋ฐœ์ƒ!

infoLog('2024-01-01', '์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต');
// [INFO] 2024-01-01: ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต

errorLogNow('๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ');
// [ERROR] 2024-01-01T...: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ

8

ํด๋กœ์ € ํ™œ์šฉ ํŒจํ„ด โ‘ข โ€” ์ปค๋ง(Currying) ๐Ÿ›

์ปค๋ง(Currying)์€ ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์ •๋ง ๋งŽ์ด ์“ฐ์ด๋Š” ๊ธฐ๋ฒ•์ด์•ผ.
์—ฌ๋Ÿฌ ์ธ์ž๋ฅผ ๋ฐ›๋Š” ํ•จ์ˆ˜๋ฅผ ํ•˜๋‚˜์˜ ์ธ์ž๋งŒ ๋ฐ›๋Š” ํ•จ์ˆ˜๋“ค์˜ ์ฒด์ธ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฑฐ์•ผ.
์ด๋ฆ„์€ ์ˆ˜ํ•™์ž Haskell Curry์—์„œ ๋”ฐ์™”์–ด. ์Œ์‹ ์นด๋ ˆ๋ž‘์€ ๊ด€๊ณ„์—†์–ด! ๐Ÿ˜„

// ์ปค๋ง ๊ธฐ๋ณธ ๊ฐœ๋…
// ์ผ๋ฐ˜ ํ•จ์ˆ˜
function add(a, b, c) {
    return a + b + c;
}
console.log(add(1, 2, 3)); // 6

// ์ปค๋ง๋œ ํ•จ์ˆ˜
function curriedAdd(a) {
    return function(b) {
        return function(c) {
            return a + b + c;
        };
    };
}
// ๋˜๋Š” ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๋กœ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ:
const curriedAddArrow = a => b => c => a + b + c;

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAddArrow(1)(2)(3)); // 6

// ์ปค๋ง์˜ ์ง„์งœ ํž˜: ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ•จ์ˆ˜ ์กฐ๊ฐ ๋งŒ๋“ค๊ธฐ
const add10 = curriedAdd(10);
const add10and20 = add10(20);

console.log(add10(5)(3));    // 18
console.log(add10and20(5));  // 35
console.log(add10and20(10)); // 40

// ์‹ค์šฉ์ ์ธ ์ปค๋ง ์˜ˆ์‹œ: ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง
const filter = predicate => array => array.filter(predicate);
const map = transform => array => array.map(transform);

const isAdult = user => user.age >= 18;
const getName = user => user.name;

const getAdultNames = array => map(getName)(filter(isAdult)(array));

const users = [
    { name: "๊น€์ฒ ์ˆ˜", age: 25 },
    { name: "์ด์˜ํฌ", age: 16 },
    { name: "๋ฐ•๋ฏผ์ค€", age: 30 },
    { name: "์ตœ์ง€์€", age: 15 }
];

console.log(getAdultNames(users)); // ["๊น€์ฒ ์ˆ˜", "๋ฐ•๋ฏผ์ค€"]
// ๋ฒ”์šฉ curry ํ•จ์ˆ˜ ๊ตฌํ˜„
function curry(fn) {
    return function curried(...args) {
        // ์ถฉ๋ถ„ํ•œ ์ธ์ž๊ฐ€ ๋ชจ์ด๋ฉด ์‹คํ–‰
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        }
        // ์•„์ง ์ธ์ž๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด ๋” ๋ฐ›์„ ์ค€๋น„
        return function(...args2) {
            return curried.apply(this, args.concat(args2));
        };
    };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const curriedMultiply = curry((a, b, c) => a * b * c);

console.log(curriedMultiply(2)(3)(4));    // 24
console.log(curriedMultiply(2, 3)(4));    // 24
console.log(curriedMultiply(2)(3, 4));    // 24
console.log(curriedMultiply(2, 3, 4));    // 24

// ์‹ค์šฉ ์˜ˆ์‹œ: ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ๊ธฐ
const calculatePrice = curry((taxRate, discount, basePrice) => {
    return basePrice * (1 + taxRate) * (1 - discount);
});

const withKoreanTax = calculatePrice(0.1);  // 10% ์„ธ๊ธˆ
const withKoreanTaxAndDiscount = withKoreanTax(0.2);  // 20% ํ• ์ธ

console.log(withKoreanTaxAndDiscount(10000)); // 8800
console.log(withKoreanTaxAndDiscount(50000)); // 44000

9

ํด๋กœ์ € ํ™œ์šฉ ํŒจํ„ด โ‘ฃ โ€” ๋ฉ”๋ชจ์ด์ œ์ด์…˜(Memoization) ๐Ÿง 

๋ฉ”๋ชจ์ด์ œ์ด์…˜(Memoization)์€ ํด๋กœ์ €์˜ ๊ฐ€์žฅ ์‹ค์šฉ์ ์ธ ํ™œ์šฉ ์ค‘ ํ•˜๋‚˜์•ผ.
์ด๋ฏธ ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œ์— ์ €์žฅํ•ด๋‘๊ณ , ๊ฐ™์€ ์ž…๋ ฅ์ด ๋“ค์–ด์˜ค๋ฉด ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๊ณ  ์บ์‹œ์—์„œ ๋ฐ”๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ธฐ๋ฒ•์ด์•ผ.
์„ฑ๋Šฅ ์ตœ์ ํ™”์— ์—„์ฒญ๋‚œ ํšจ๊ณผ๊ฐ€ ์žˆ์–ด! โšก

// ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ์—†๋Š” ํ”ผ๋ณด๋‚˜์น˜ โ€” ์—„์ฒญ ๋А๋ ค!
function fibSlow(n) {
    if (n <= 1) return n;
    return fibSlow(n - 1) + fibSlow(n - 2);
}
// fibSlow(40) โ†’ ์•ฝ 1์–ต ๋ฒˆ ์ด์ƒ์˜ ํ•จ์ˆ˜ ํ˜ธ์ถœ! ๐Ÿ˜ฑ

// ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ์ ์šฉ ํ”ผ๋ณด๋‚˜์น˜
function memoize(fn) {
    const cache = new Map(); // ํด๋กœ์ €๋กœ ์บ์‹œ ์œ ์ง€!
    
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            console.log(`์บ์‹œ ํžˆํŠธ! key: ${key}`);
            return cache.get(key);
        }
        
        const result = fn.apply(this, args);
        cache.set(key, result);
        return result;
    };
}

// ๋ฉ”๋ชจ์ด์ œ์ด์…˜๋œ ํ”ผ๋ณด๋‚˜์น˜
const fib = memoize(function(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2); // ์žฌ๊ท€ ํ˜ธ์ถœ๋„ ์บ์‹œ ํ™œ์šฉ!
});

console.time('fib(40)');
console.log(fib(40)); // 102334155
console.timeEnd('fib(40)'); // ๊ฑฐ์˜ ์ฆ‰์‹œ!

console.time('fib(40) ์žฌํ˜ธ์ถœ');
console.log(fib(40)); // 102334155 โ€” ์บ์‹œ์—์„œ ์ฆ‰์‹œ ๋ฐ˜ํ™˜!
console.timeEnd('fib(40) ์žฌํ˜ธ์ถœ'); // 0ms์— ๊ฐ€๊นŒ์›€!
// ์‹ค์šฉ์ ์ธ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ์˜ˆ์‹œ: API ๊ฒฐ๊ณผ ์บ์‹ฑ
function createCachedFetcher(ttl = 5 * 60 * 1000) { // ๊ธฐ๋ณธ 5๋ถ„ ์บ์‹œ
    const cache = new Map();
    
    return async function fetchWithCache(url) {
        const now = Date.now();
        
        if (cache.has(url)) {
            const { data, timestamp } = cache.get(url);
            
            // TTL ๋‚ด์— ์žˆ์œผ๋ฉด ์บ์‹œ ๋ฐ˜ํ™˜
            if (now - timestamp < ttl) {
                console.log(`์บ์‹œ์—์„œ ๋ฐ˜ํ™˜: ${url}`);
                return data;
            }
            
            // TTL ๋งŒ๋ฃŒ ์‹œ ์บ์‹œ ์‚ญ์ œ
            cache.delete(url);
        }
        
        console.log(`์‹ค์ œ ์š”์ฒญ: ${url}`);
        const response = await fetch(url);
        const data = await response.json();
        
        cache.set(url, { data, timestamp: now });
        return data;
    };
}

const cachedFetch = createCachedFetcher(60000); // 1๋ถ„ ์บ์‹œ

// ๊ฐ™์€ URL ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•ด๋„ 1๋ถ„ ๋‚ด์—๋Š” ์บ์‹œ ๋ฐ˜ํ™˜!
// await cachedFetch('https://api.example.com/users');
// await cachedFetch('https://api.example.com/users'); // ์บ์‹œ ๋ฐ˜ํ™˜!

// ๊ณ ๊ธ‰: WeakMap์„ ์ด์šฉํ•œ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ (๊ฐ์ฒด ์ธ์ž์šฉ)
function memoizeWithWeakMap(fn) {
    const cache = new WeakMap();
    
    return function(obj) {
        if (cache.has(obj)) {
            return cache.get(obj);
        }
        const result = fn(obj);
        cache.set(obj, result);
        return result;
    };
}
๐Ÿ’ก ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ! ๋ฉ”๋ชจ์ด์ œ์ด์…˜์€ ์ˆœ์ˆ˜ ํ•จ์ˆ˜(Pure Function)์—๋งŒ ์ ์šฉํ•ด์•ผ ํ•ด.
๊ฐ™์€ ์ž…๋ ฅ์— ํ•ญ์ƒ ๊ฐ™์€ ์ถœ๋ ฅ์„ ๋‚ด๋Š” ํ•จ์ˆ˜์—ฌ์•ผ ์บ์‹ฑ์ด ์˜๋ฏธ ์žˆ์–ด.
๋ถ€์ž‘์šฉ(Side Effect)์ด ์žˆ๋Š” ํ•จ์ˆ˜์— ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ์ ์šฉํ•˜๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฒ„๊ทธ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์–ด!

10

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ํด๋กœ์ € โ€” ์‹ค์ „ DOM ํ™œ์šฉ ๐Ÿ–ฑ๏ธ

์›น ๊ฐœ๋ฐœ์—์„œ ํด๋กœ์ €๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ •๋ง ๋งŽ์ด ์“ฐ์—ฌ.
ํŠนํžˆ ๋ฃจํ”„์—์„œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•  ๋•Œ ํด๋กœ์ €๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๋ฉด
์•„์ฃผ ์œ ๋ช…ํ•œ ๋ฒ„๊ทธ๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋ผ! ๐Ÿ˜ฑ

// โŒ ์œ ๋ช…ํ•œ ํด๋กœ์ € ๋ฒ„๊ทธ โ€” var๋ฅผ ์“ธ ๋•Œ
for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i); // 5, 5, 5, 5, 5 ์ถœ๋ ฅ! ๐Ÿ˜ฑ
    }, 1000);
}
// ์™œ? var๋Š” ํ•จ์ˆ˜ ์Šค์ฝ”ํ”„๋ผ ๋ฃจํ”„๊ฐ€ ๋๋‚œ ํ›„ i = 5๊ฐ€ ๋˜์–ด๋ฒ„๋ฆผ!
// ๋ชจ๋“  setTimeout ์ฝœ๋ฐฑ์ด ๊ฐ™์€ i๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์–ด!

// โœ… ํ•ด๊ฒฐ์ฑ… 1: let ์‚ฌ์šฉ (๋ธ”๋ก ์Šค์ฝ”ํ”„)
for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i); // 0, 1, 2, 3, 4 ์ถœ๋ ฅ! โœ…
    }, 1000);
}
// let์€ ๋ธ”๋ก ์Šค์ฝ”ํ”„๋ผ ๊ฐ ๋ฐ˜๋ณต๋งˆ๋‹ค ์ƒˆ๋กœ์šด i๊ฐ€ ์ƒ์„ฑ๋ผ!

// โœ… ํ•ด๊ฒฐ์ฑ… 2: IIFE๋กœ ํด๋กœ์ € ์ƒ์„ฑ (๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ ๋Œ€์‘)
for (var i = 0; i < 5; i++) {
    (function(j) { // j๋Š” ๊ฐ ๋ฐ˜๋ณต์˜ i ๊ฐ’์„ ์บก์ฒ˜!
        setTimeout(function() {
            console.log(j); // 0, 1, 2, 3, 4 ์ถœ๋ ฅ! โœ…
        }, 1000);
    })(i);
}

// โœ… ํ•ด๊ฒฐ์ฑ… 3: bind ์‚ฌ์šฉ
for (var i = 0; i < 5; i++) {
    setTimeout(console.log.bind(null, i), 1000);
    // 0, 1, 2, 3, 4 ์ถœ๋ ฅ! โœ…
}
// ์‹ค์ „ ์˜ˆ์‹œ: ๋ฒ„ํŠผ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ์— ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ
function setupButtons(items) {
    items.forEach((item, index) => {
        const button = document.createElement('button');
        button.textContent = item.name;
        
        // ํด๋กœ์ €๋กœ item๊ณผ index๋ฅผ ์บก์ฒ˜!
        button.addEventListener('click', function() {
            console.log(`ํด๋ฆญ๋œ ์•„์ดํ…œ: ${item.name}, ์ธ๋ฑ์Šค: ${index}`);
            handleItemClick(item);
        });
        
        document.body.appendChild(button);
    });
}

// ๊ณ ๊ธ‰: ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํŒฉํ† ๋ฆฌ
function createClickHandler(itemId, onSuccess, onError) {
    let clickCount = 0; // ํด๋กœ์ €๋กœ ํด๋ฆญ ํšŸ์ˆ˜ ์ถ”์ !
    
    return async function handleClick(event) {
        clickCount++;
        console.log(`${itemId} ํด๋ฆญ ํšŸ์ˆ˜: ${clickCount}`);
        
        // ๋”๋ธ” ํด๋ฆญ ๋ฐฉ์ง€
        if (clickCount > 1) {
            console.log('์ด๋ฏธ ์ฒ˜๋ฆฌ ์ค‘์ž…๋‹ˆ๋‹ค!');
            return;
        }
        
        try {
            const result = await processItem(itemId);
            onSuccess(result);
        } catch (error) {
            onError(error);
        } finally {
            clickCount = 0; // ์ฒ˜๋ฆฌ ์™„๋ฃŒ ํ›„ ๋ฆฌ์…‹
        }
    };
}

// ์ด๋ฒคํŠธ ์œ„์ž„ + ํด๋กœ์ €
function createEventDelegator(container, selector, handler) {
    // handler๋ฅผ ํด๋กœ์ €๋กœ ์บก์ฒ˜
    container.addEventListener('click', function(event) {
        const target = event.target.closest(selector);
        if (target && container.contains(target)) {
            handler(target, event);
        }
    });
}

11

๊ณ ๊ธ‰ ํŒจํ„ด โ€” ํด๋กœ์ €๋กœ ๊ตฌํ˜„ํ•˜๋Š” ์˜ต์ €๋ฒ„ ํŒจํ„ด ๐Ÿ‘๏ธ

์˜ต์ €๋ฒ„ ํŒจํ„ด(Observer Pattern)์€ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๋ฐ˜์‘ํ•˜๋Š” ํŒจํ„ด์ด์•ผ.
React์˜ useState, Vue์˜ reactive ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์ด ํŒจํ„ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด.
ํด๋กœ์ €๋ฅผ ์ด์šฉํ•˜๋ฉด ์ด๊ฑธ ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽฏ

// ํด๋กœ์ €๋ฅผ ์ด์šฉํ•œ ๋ฐ˜์‘ํ˜• ์ƒํƒœ ๊ด€๋ฆฌ
function createReactiveState(initialState) {
    let state = { ...initialState };
    const listeners = new Map();
    
    function notify(key, newValue, oldValue) {
        if (listeners.has(key)) {
            listeners.get(key).forEach(listener => {
                listener(newValue, oldValue);
            });
        }
        // ์ „์ฒด ๋ณ€๊ฒฝ ๋ฆฌ์Šค๋„ˆ
        if (listeners.has('*')) {
            listeners.get('*').forEach(listener => {
                listener({ key, newValue, oldValue });
            });
        }
    }
    
    return {
        // ์ƒํƒœ ์ฝ๊ธฐ
        get(key) {
            return state[key];
        },
        
        // ์ƒํƒœ ์“ฐ๊ธฐ (๋ณ€๊ฒฝ ๊ฐ์ง€!)
        set(key, value) {
            const oldValue = state[key];
            if (oldValue === value) return; // ๋ณ€๊ฒฝ ์—†์œผ๋ฉด ๋ฌด์‹œ
            
            state[key] = value;
            notify(key, value, oldValue);
        },
        
        // ๋ณ€๊ฒฝ ๊ตฌ๋…
        subscribe(key, listener) {
            if (!listeners.has(key)) {
                listeners.set(key, new Set());
            }
            listeners.get(key).add(listener);
            
            // ๊ตฌ๋… ์ทจ์†Œ ํ•จ์ˆ˜ ๋ฐ˜ํ™˜ (ํด๋กœ์ €!)
            return function unsubscribe() {
                listeners.get(key).delete(listener);
            };
        },
        
        // ์ „์ฒด ์ƒํƒœ ์Šค๋ƒ…์ƒท
        getState() {
            return { ...state };
        }
    };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const store = createReactiveState({
    count: 0,
    userName: "๊น€์ฒ ์ˆ˜",
    isLoggedIn: false
});

// count ๋ณ€๊ฒฝ ๊ตฌ๋…
const unsubCount = store.subscribe('count', (newVal, oldVal) => {
    console.log(`count ๋ณ€๊ฒฝ: ${oldVal} โ†’ ${newVal}`);
});

// ์ „์ฒด ๋ณ€๊ฒฝ ๊ตฌ๋…
store.subscribe('*', ({ key, newValue }) => {
    console.log(`์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ์ง€: ${key} = ${newValue}`);
});

store.set('count', 1);
// "count ๋ณ€๊ฒฝ: 0 โ†’ 1"
// "์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ์ง€: count = 1"

store.set('userName', '์ด์˜ํฌ');
// "์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ์ง€: userName = ์ด์˜ํฌ"

// ๊ตฌ๋… ์ทจ์†Œ
unsubCount();
store.set('count', 2); // count ๋ฆฌ์Šค๋„ˆ๋Š” ๋” ์ด์ƒ ํ˜ธ์ถœ ์•ˆ ๋จ!
// "์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ์ง€: count = 2" (์ „์ฒด ๋ฆฌ์Šค๋„ˆ๋งŒ ํ˜ธ์ถœ)

12

ํด๋กœ์ €์™€ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ โ€” Promise, async/await์™€์˜ ๋งŒ๋‚จ โณ

๋น„๋™๊ธฐ ์ฝ”๋“œ์—์„œ๋„ ํด๋กœ์ €๋Š” ํ•ต์‹ฌ์ ์ธ ์—ญํ• ์„ ํ•ด.
Promise ์ฒด์ธ์ด๋‚˜ async/await์—์„œ ์™ธ๋ถ€ ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐํ•  ๋•Œ ํด๋กœ์ €๊ฐ€ ๋™์ž‘ํ•˜๊ณ  ์žˆ์–ด.
์ด๊ฑธ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๋ฉด ๋น„๋™๊ธฐ ์ฝ”๋“œ์˜ ๋ฒ„๊ทธ๋ฅผ ํ›จ์”ฌ ์‰ฝ๊ฒŒ ์žก์„ ์ˆ˜ ์žˆ์–ด! ๐Ÿ›

// ๋น„๋™๊ธฐ ํด๋กœ์ € ์˜ˆ์‹œ 1: ์žฌ์‹œ๋„ ๋กœ์ง
function createRetryFetcher(maxRetries = 3, delay = 1000) {
    // maxRetries์™€ delay๋ฅผ ํด๋กœ์ €๋กœ ์บก์ฒ˜!
    
    return async function fetchWithRetry(url, options = {}) {
        let lastError;
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                console.log(`์‹œ๋„ ${attempt}/${maxRetries}: ${url}`);
                const response = await fetch(url, options);
                
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}`);
                }
                
                return await response.json();
                
            } catch (error) {
                lastError = error;
                console.log(`์‹œ๋„ ${attempt} ์‹คํŒจ: ${error.message}`);
                
                if (attempt < maxRetries) {
                    // ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„: ์žฌ์‹œ๋„๋งˆ๋‹ค ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์ฆ๊ฐ€
                    const waitTime = delay * Math.pow(2, attempt - 1);
                    console.log(`${waitTime}ms ํ›„ ์žฌ์‹œ๋„...`);
                    await new Promise(resolve => setTimeout(resolve, waitTime));
                }
            }
        }
        
        throw new Error(`${maxRetries}๋ฒˆ ์‹œ๋„ ํ›„ ์‹คํŒจ: ${lastError.message}`);
    };
}

const robustFetch = createRetryFetcher(3, 500);
// robustFetch('https://api.example.com/data');

// ๋น„๋™๊ธฐ ํด๋กœ์ € ์˜ˆ์‹œ 2: ์š”์ฒญ ์ทจ์†Œ ๊ฐ€๋Šฅํ•œ fetcher
function createCancellableFetch() {
    let currentController = null;
    
    return {
        async fetch(url, options = {}) {
            // ์ด์ „ ์š”์ฒญ ์ทจ์†Œ
            if (currentController) {
                currentController.abort();
            }
            
            currentController = new AbortController();
            
            try {
                const response = await fetch(url, {
                    ...options,
                    signal: currentController.signal
                });
                currentController = null;
                return await response.json();
                
            } catch (error) {
                if (error.name === 'AbortError') {
                    console.log('์š”์ฒญ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
                    return null;
                }
                throw error;
            }
        },
        
        cancel() {
            if (currentController) {
                currentController.abort();
                currentController = null;
            }
        }
    };
}

const searchFetcher = createCancellableFetch();
// ๊ฒ€์ƒ‰์ฐฝ์—์„œ ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค ์ด์ „ ์š”์ฒญ ์ทจ์†Œํ•˜๊ณ  ์ƒˆ ์š”์ฒญ!
// searchFetcher.fetch(`/api/search?q=${query}`);
// ๋น„๋™๊ธฐ ํด๋กœ์ € ์˜ˆ์‹œ 3: ๋””๋ฐ”์šด์Šค์™€ ์Šค๋กœํ‹€
// ๋””๋ฐ”์šด์Šค: ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ ํ›„ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜์•ผ ์‹คํ–‰
function debounce(fn, delay) {
    let timeoutId; // ํด๋กœ์ €๋กœ ํƒ€์ด๋จธ ID ์œ ์ง€!
    
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

// ์Šค๋กœํ‹€: ์ผ์ • ์‹œ๊ฐ„ ๋‚ด์— ์ตœ๋Œ€ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰
function throttle(fn, limit) {
    let lastRun = 0; // ํด๋กœ์ €๋กœ ๋งˆ์ง€๋ง‰ ์‹คํ–‰ ์‹œ๊ฐ„ ์œ ์ง€!
    let timeoutId;
    
    return function(...args) {
        const now = Date.now();
        const remaining = limit - (now - lastRun);
        
        if (remaining <= 0) {
            clearTimeout(timeoutId);
            lastRun = now;
            fn.apply(this, args);
        } else {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => {
                lastRun = Date.now();
                fn.apply(this, args);
            }, remaining);
        }
    };
}

// ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ
const handleSearch = debounce(async (query) => {
    console.log(`๊ฒ€์ƒ‰ ์‹คํ–‰: ${query}`);
    // const results = await searchApi(query);
}, 300);

const handleScroll = throttle(() => {
    console.log('์Šคํฌ๋กค ์ฒ˜๋ฆฌ:', window.scrollY);
}, 100);

// document.addEventListener('scroll', handleScroll);
// searchInput.addEventListener('input', e => handleSearch(e.target.value));
๐Ÿ’ก ๋””๋ฐ”์šด์Šค vs ์Šค๋กœํ‹€ ์–ธ์ œ ์จ? ๋””๋ฐ”์šด์Šค: ๊ฒ€์ƒ‰์ฐฝ ์ž๋™์™„์„ฑ, ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์ฒ˜๋Ÿผ "๋งˆ์ง€๋ง‰ ์ž…๋ ฅ ํ›„ ์ฒ˜๋ฆฌ"๊ฐ€ ํ•„์š”ํ•  ๋•Œ
์Šค๋กœํ‹€: ์Šคํฌ๋กค ์ด๋ฒคํŠธ, ๋งˆ์šฐ์Šค ์ด๋™์ฒ˜๋Ÿผ "์ผ์ • ๊ฐ„๊ฒฉ์œผ๋กœ ์ฒ˜๋ฆฌ"๊ฐ€ ํ•„์š”ํ•  ๋•Œ

13

ํด๋กœ์ €์™€ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ โ€” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€ํ•˜๊ธฐ ๐Ÿ—‘๏ธ

ํด๋กœ์ €๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜(Memory Leak)๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์–ด.
ํด๋กœ์ €๊ฐ€ ์™ธ๋ถ€ ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์œผ๋ฉด ๊ทธ ๋ณ€์ˆ˜๋Š” ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜์ด ์•ˆ ๋˜๊ฑฐ๋“ .
์ด๊ฑธ ์ดํ•ดํ•˜๊ณ  ์ ์ ˆํžˆ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด! ๐Ÿ”ง

// โŒ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์˜ˆ์‹œ
function createLeakyHandler() {
    const hugeData = new Array(1000000).fill('๋ฐ์ดํ„ฐ'); // ํฐ ๋ฐ์ดํ„ฐ!
    
    return function handler(event) {
        // hugeData๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋„ ์•Š๋Š”๋ฐ ํด๋กœ์ €๋กœ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์–ด!
        console.log('ํด๋ฆญ!');
    };
}

const button = document.getElementById('myButton');
const handler = createLeakyHandler();
button.addEventListener('click', handler);
// handler๊ฐ€ ์‚ด์•„์žˆ๋Š” ํ•œ hugeData๋„ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€๋จ! ๐Ÿ˜ฑ

// โœ… ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€
function createEfficientHandler() {
    const hugeData = new Array(1000000).fill('๋ฐ์ดํ„ฐ');
    
    // ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ถ”์ถœํ•ด์„œ ์‚ฌ์šฉ
    const processedData = hugeData.length; // ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ์ถ”์ถœ
    // hugeData๋Š” ์ด ํ•จ์ˆ˜ ์‹คํ–‰ ํ›„ GC ๋Œ€์ƒ์ด ๋จ!
    
    return function handler(event) {
        console.log(`ํด๋ฆญ! ๋ฐ์ดํ„ฐ ์ˆ˜: ${processedData}`);
    };
}

// โœ… ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ •๋ฆฌ
class Component {
    constructor(element) {
        this.element = element;
        this.handlers = []; // ํ•ธ๋“ค๋Ÿฌ ๋ชฉ๋ก ๊ด€๋ฆฌ
        
        this.setupEvents();
    }
    
    setupEvents() {
        const clickHandler = this.handleClick.bind(this);
        const scrollHandler = throttle(this.handleScroll.bind(this), 100);
        
        this.element.addEventListener('click', clickHandler);
        window.addEventListener('scroll', scrollHandler);
        
        // ๋‚˜์ค‘์— ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ €์žฅ
        this.handlers.push(
            { target: this.element, type: 'click', handler: clickHandler },
            { target: window, type: 'scroll', handler: scrollHandler }
        );
    }
    
    handleClick(event) {
        console.log('ํด๋ฆญ!');
    }
    
    handleScroll(event) {
        console.log('์Šคํฌ๋กค!');
    }
    
    // ์ปดํฌ๋„ŒํŠธ ์ œ๊ฑฐ ์‹œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ •๋ฆฌ!
    destroy() {
        this.handlers.forEach(({ target, type, handler }) => {
            target.removeEventListener(type, handler);
        });
        this.handlers = [];
        this.element = null;
    }
}
โš ๏ธ ํด๋กœ์ € ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์ฃผ์˜ ์ƒํ™ฉ๋“ค 1. ์ „์—ญ ๋ณ€์ˆ˜์— ํด๋กœ์ € ์ €์žฅ: ์ „์—ญ ๋ณ€์ˆ˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ์‚ด์•„์žˆ์–ด
2. ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ฏธ์ œ๊ฑฐ: DOM ์š”์†Œ๊ฐ€ ์ œ๊ฑฐ๋˜์–ด๋„ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋‚จ์•„์žˆ์œผ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜
3. ํƒ€์ด๋จธ ๋ฏธ์ •๋ฆฌ: setInterval์˜ ์ฝœ๋ฐฑ์ด ํด๋กœ์ €๋ฅผ ํ†ตํ•ด ํฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•˜๋ฉด ์œ„ํ—˜
4. ์ˆœํ™˜ ์ฐธ์กฐ: ํด๋กœ์ €๊ฐ€ ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒฝ์šฐ

14

์‹ค์ „ ํ”„๋กœ์ ํŠธ ํŒจํ„ด โ€” ํด๋กœ์ €๋กœ ๋งŒ๋“œ๋Š” ๋ฏธ๋‹ˆ ํ”„๋ ˆ์ž„์›Œํฌ ๐Ÿ—๏ธ

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๊ฒƒ๋“ค์„ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” ํŒจํ„ด๋“ค์„ ๋งŒ๋“ค์–ด๋ณด์ž!
์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์ฒ˜๋Ÿผ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•  ๋•Œ๋„
์ด๋Ÿฐ ํŒจํ„ด๋“ค์ด ์‹ค์ œ๋กœ ๋งŽ์ด ์“ฐ์—ฌ. ๐Ÿ˜Š

// ์‹ค์ „ ํŒจํ„ด 1: ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ
function createPluginSystem() {
    const plugins = new Map();
    const hooks = new Map();
    
    return {
        // ํ”Œ๋Ÿฌ๊ทธ์ธ ๋“ฑ๋ก
        register(name, plugin) {
            if (plugins.has(name)) {
                throw new Error(`ํ”Œ๋Ÿฌ๊ทธ์ธ '${name}'์ด ์ด๋ฏธ ๋“ฑ๋ก๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.`);
            }
            plugins.set(name, plugin);
            
            // ํ”Œ๋Ÿฌ๊ทธ์ธ ์ดˆ๊ธฐํ™”
            if (typeof plugin.install === 'function') {
                plugin.install(this);
            }
            
            return this; // ์ฒด์ด๋‹ ์ง€์›!
        },
        
        // ํ›… ๋“ฑ๋ก
        addHook(hookName, callback) {
            if (!hooks.has(hookName)) {
                hooks.set(hookName, []);
            }
            hooks.get(hookName).push(callback);
            
            return function removeHook() {
                const hookList = hooks.get(hookName);
                const index = hookList.indexOf(callback);
                if (index > -1) hookList.splice(index, 1);
            };
        },
        
        // ํ›… ์‹คํ–‰
        async runHook(hookName, ...args) {
            if (!hooks.has(hookName)) return args[0];
            
            let result = args[0];
            for (const hook of hooks.get(hookName)) {
                result = await hook(result, ...args.slice(1)) ?? result;
            }
            return result;
        },
        
        // ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐ€์ ธ์˜ค๊ธฐ
        getPlugin(name) {
            return plugins.get(name);
        }
    };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const app = createPluginSystem();

// ๋กœ๊น… ํ”Œ๋Ÿฌ๊ทธ์ธ
const LoggerPlugin = {
    install(system) {
        system.addHook('beforeRequest', (url) => {
            console.log(`[LOG] ์š”์ฒญ ์‹œ์ž‘: ${url}`);
            return url;
        });
        
        system.addHook('afterRequest', (response) => {
            console.log(`[LOG] ์š”์ฒญ ์™„๋ฃŒ:`, response);
            return response;
        });
    }
};

// ์ธ์ฆ ํ”Œ๋Ÿฌ๊ทธ์ธ
const AuthPlugin = {
    install(system) {
        system.addHook('beforeRequest', (url) => {
            const token = localStorage.getItem('token');
            return token ? `${url}?token=${token}` : url;
        });
    }
};

app.register('logger', LoggerPlugin)
   .register('auth', AuthPlugin);
// ์‹ค์ „ ํŒจํ„ด 2: ์ƒํƒœ ๋จธ์‹  (State Machine)
function createStateMachine(config) {
    const { initial, states } = config;
    let currentState = initial;
    const listeners = new Set();
    
    function transition(event) {
        const stateConfig = states[currentState];
        
        if (!stateConfig || !stateConfig.on || !stateConfig.on[event]) {
            console.warn(`์ƒํƒœ '${currentState}'์—์„œ ์ด๋ฒคํŠธ '${event}'๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.`);
            return false;
        }
        
        const nextState = stateConfig.on[event];
        const prevState = currentState;
        
        // exit ์•ก์…˜ ์‹คํ–‰
        if (stateConfig.exit) {
            stateConfig.exit({ from: prevState, to: nextState, event });
        }
        
        currentState = nextState;
        
        // enter ์•ก์…˜ ์‹คํ–‰
        const nextStateConfig = states[nextState];
        if (nextStateConfig && nextStateConfig.enter) {
            nextStateConfig.enter({ from: prevState, to: nextState, event });
        }
        
        // ๋ฆฌ์Šค๋„ˆ ์•Œ๋ฆผ
        listeners.forEach(listener => {
            listener({ from: prevState, to: nextState, event });
        });
        
        return true;
    }
    
    return {
        send: transition,
        
        getState() {
            return currentState;
        },
        
        matches(state) {
            return currentState === state;
        },
        
        onTransition(listener) {
            listeners.add(listener);
            return () => listeners.delete(listener);
        }
    };
}

// ์ฃผ๋ฌธ ์ƒํƒœ ๋จธ์‹  ์˜ˆ์‹œ
const orderMachine = createStateMachine({
    initial: '๋Œ€๊ธฐ์ค‘',
    states: {
        '๋Œ€๊ธฐ์ค‘': {
            on: { '๊ฒฐ์ œ์™„๋ฃŒ': '๊ฒฐ์ œ๋จ' },
            enter: () => console.log('์ฃผ๋ฌธ ๋Œ€๊ธฐ ์ค‘...')
        },
        '๊ฒฐ์ œ๋จ': {
            on: { '๋ฐฐ์†ก์‹œ์ž‘': '๋ฐฐ์†ก์ค‘', '์ทจ์†Œ': '์ทจ์†Œ๋จ' },
            enter: () => console.log('๊ฒฐ์ œ ์™„๋ฃŒ! ๋ฐฐ์†ก ์ค€๋น„ ์ค‘...')
        },
        '๋ฐฐ์†ก์ค‘': {
            on: { '๋ฐฐ์†ก์™„๋ฃŒ': '์™„๋ฃŒ๋จ' },
            enter: () => console.log('๋ฐฐ์†ก ์‹œ์ž‘!')
        },
        '์™„๋ฃŒ๋จ': {
            enter: () => console.log('๋ฐฐ์†ก ์™„๋ฃŒ! ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค ๐ŸŽ‰')
        },
        '์ทจ์†Œ๋จ': {
            enter: () => console.log('์ฃผ๋ฌธ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.')
        }
    }
});

orderMachine.onTransition(({ from, to, event }) => {
    console.log(`์ƒํƒœ ๋ณ€๊ฒฝ: ${from} โ†’ ${to} (์ด๋ฒคํŠธ: ${event})`);
});

orderMachine.send('๊ฒฐ์ œ์™„๋ฃŒ');
// "๊ฒฐ์ œ ์™„๋ฃŒ! ๋ฐฐ์†ก ์ค€๋น„ ์ค‘..."
// "์ƒํƒœ ๋ณ€๊ฒฝ: ๋Œ€๊ธฐ์ค‘ โ†’ ๊ฒฐ์ œ๋จ (์ด๋ฒคํŠธ: ๊ฒฐ์ œ์™„๋ฃŒ)"

orderMachine.send('๋ฐฐ์†ก์‹œ์ž‘');
// "๋ฐฐ์†ก ์‹œ์ž‘!"
// "์ƒํƒœ ๋ณ€๊ฒฝ: ๊ฒฐ์ œ๋จ โ†’ ๋ฐฐ์†ก์ค‘ (์ด๋ฒคํŠธ: ๋ฐฐ์†ก์‹œ์ž‘)"

15

ํด๋กœ์ € ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ โšก

ํด๋กœ์ €๋ฅผ ์ž˜ ์“ฐ๋ฉด ์„ฑ๋Šฅ๋„ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ์–ด!
๋ช‡ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์ตœ์ ํ™” ํŒ์„ ์•Œ์•„๋ณด์ž. ๐Ÿš€

์ตœ์ ํ™” ๊ธฐ๋ฒ• ์„ค๋ช… ํšจ๊ณผ
์ „์—ญ ๋ณ€์ˆ˜ ์ง€์—ญํ™” ์ž์ฃผ ์“ฐ๋Š” ์ „์—ญ ๋ณ€์ˆ˜๋ฅผ ์ง€์—ญ ๋ณ€์ˆ˜์— ์บ์‹ฑ ์Šค์ฝ”ํ”„ ์ฒด์ธ ํƒ์ƒ‰ ๊ฐ์†Œ
๋ฉ”๋ชจ์ด์ œ์ด์…˜ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ํด๋กœ์ €๋กœ ์บ์‹ฑ ์ค‘๋ณต ๊ณ„์‚ฐ ์ œ๊ฑฐ
์ง€์—ฐ ์ดˆ๊ธฐํ™” ์ฒ˜์Œ ํ•„์š”ํ•  ๋•Œ๋งŒ ์ดˆ๊ธฐํ™” ๋ถˆํ•„์š”ํ•œ ์ดˆ๊ธฐ ๋น„์šฉ ์ œ๊ฑฐ
ํด๋กœ์ € ์ตœ์†Œํ™” ํ•„์š”ํ•œ ๋ณ€์ˆ˜๋งŒ ํด๋กœ์ €์— ํฌํ•จ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๊ฐ์†Œ
// ์„ฑ๋Šฅ ์ตœ์ ํ™” ์˜ˆ์‹œ 1: ์ง€์—ฐ ์ดˆ๊ธฐํ™” (Lazy Initialization)
function createExpensiveResource() {
    let resource = null; // ์•„์ง ์ดˆ๊ธฐํ™” ์•ˆ ํ•จ!
    
    return {
        getResource() {
            // ์ฒ˜์Œ ํ˜ธ์ถœ๋  ๋•Œ๋งŒ ์ดˆ๊ธฐํ™”!
            if (resource === null) {
                console.log('๋ฆฌ์†Œ์Šค ์ดˆ๊ธฐํ™” ์ค‘... (ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋จ)');
                resource = {
                    data: new Array(10000).fill(0).map((_, i) => i * i),
                    timestamp: Date.now()
                };
            }
            return resource;
        },
        
        resetResource() {
            resource = null; // ํ•„์š”์‹œ ๋ฆฌ์…‹ ๊ฐ€๋Šฅ
        }
    };
}

const expensiveResource = createExpensiveResource();
// ์•„์ง ์ดˆ๊ธฐํ™” ์•ˆ ๋จ!

// ์ฒ˜์Œ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ์ดˆ๊ธฐํ™”
const r1 = expensiveResource.getResource(); // "๋ฆฌ์†Œ์Šค ์ดˆ๊ธฐํ™” ์ค‘..."
const r2 = expensiveResource.getResource(); // ์บ์‹œ์—์„œ ๋ฐ˜ํ™˜ (์ดˆ๊ธฐํ™” ์—†์Œ!)
console.log(r1 === r2); // true โ€” ๊ฐ™์€ ๊ฐ์ฒด!

// ์„ฑ๋Šฅ ์ตœ์ ํ™” ์˜ˆ์‹œ 2: ์ „์—ญ ๋ณ€์ˆ˜ ์ง€์—ญํ™”
function processLargeArray(arr) {
    // ์ „์—ญ Math ๊ฐ์ฒด๋ฅผ ์ง€์—ญ ๋ณ€์ˆ˜์— ์บ์‹ฑ
    const { floor, sqrt, abs } = Math;
    const len = arr.length; // length๋„ ์บ์‹ฑ
    
    const results = new Array(len);
    
    for (let i = 0; i < len; i++) {
        // ๋งค๋ฒˆ Math.floor, Math.sqrt ํƒ์ƒ‰ ์—†์ด ๋ฐ”๋กœ ์ ‘๊ทผ!
        results[i] = floor(sqrt(abs(arr[i])));
    }
    
    return results;
}

// ์„ฑ๋Šฅ ์ตœ์ ํ™” ์˜ˆ์‹œ 3: ํ•จ์ˆ˜ ํ•ฉ์„ฑ ์ตœ์ ํ™”
function compose(...fns) {
    // fns ๋ฐฐ์—ด์„ ํด๋กœ์ €๋กœ ์บก์ฒ˜
    return function(x) {
        return fns.reduceRight((acc, fn) => fn(acc), x);
    };
}

function pipe(...fns) {
    return function(x) {
        return fns.reduce((acc, fn) => fn(acc), x);
    };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const processUser = pipe(
    user => ({ ...user, name: user.name.trim() }),
    user => ({ ...user, email: user.email.toLowerCase() }),
    user => ({ ...user, age: parseInt(user.age) }),
    user => ({ ...user, isAdult: user.age >= 18 })
);

const user = processUser({
    name: "  ๊น€์ฒ ์ˆ˜  ",
    email: "KIM@EXAMPLE.COM",
    age: "25"
});
console.log(user);
// { name: "๊น€์ฒ ์ˆ˜", email: "kim@example.com", age: 25, isAdult: true }

16

ํด๋กœ์ € ๋””๋ฒ„๊น… โ€” ๋ฌธ์ œ ์ฐพ๊ณ  ํ•ด๊ฒฐํ•˜๊ธฐ ๐Ÿ›

ํด๋กœ์ € ๊ด€๋ จ ๋ฒ„๊ทธ๋Š” ์ฒ˜์Œ์— ์ •๋ง ์ฐพ๊ธฐ ์–ด๋ ค์›Œ.
ํ•˜์ง€๋งŒ ํŒจํ„ด์„ ์•Œ๋ฉด ๊ธˆ๋ฐฉ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด! ๐Ÿ”
๊ฐ€์žฅ ํ”ํ•œ ํด๋กœ์ € ๋ฒ„๊ทธ๋“ค๊ณผ ํ•ด๊ฒฐ์ฑ…์„ ์‚ดํŽด๋ณด์ž.

// ๋ฒ„๊ทธ 1: ๋ฃจํ”„์—์„œ์˜ ํด๋กœ์ € (์ด๋ฏธ ๋ดค์ง€๋งŒ ๋‹ค์‹œ!)
// โŒ ๋ฌธ์ œ
const funcs = [];
for (var i = 0; i < 3; i++) {
    funcs.push(function() { return i; });
}
console.log(funcs[0]()); // 3 (์˜ˆ์ƒ: 0)
console.log(funcs[1]()); // 3 (์˜ˆ์ƒ: 1)
console.log(funcs[2]()); // 3 (์˜ˆ์ƒ: 2)

// โœ… ํ•ด๊ฒฐ: let ์‚ฌ์šฉ
const funcs2 = [];
for (let i = 0; i < 3; i++) {
    funcs2.push(function() { return i; });
}
console.log(funcs2[0]()); // 0 โœ…
console.log(funcs2[1]()); // 1 โœ…
console.log(funcs2[2]()); // 2 โœ…

// ๋ฒ„๊ทธ 2: this ๋ฐ”์ธ๋”ฉ๊ณผ ํด๋กœ์ € ํ˜ผ๋™
class Timer {
    constructor() {
        this.count = 0;
    }
    
    // โŒ ๋ฌธ์ œ: ์ผ๋ฐ˜ ํ•จ์ˆ˜์—์„œ this๊ฐ€ ๋‹ฌ๋ผ์ง
    startBroken() {
        setInterval(function() {
            this.count++; // this๊ฐ€ Timer ์ธ์Šคํ„ด์Šค๊ฐ€ ์•„๋‹˜!
            console.log(this.count); // NaN ๋˜๋Š” ์—๋Ÿฌ
        }, 1000);
    }
    
    // โœ… ํ•ด๊ฒฐ 1: ํ™”์‚ดํ‘œ ํ•จ์ˆ˜ (this๋ฅผ ๋ ‰์‹œ์ปฌ๋กœ ๋ฐ”์ธ๋”ฉ)
    startFixed1() {
        setInterval(() => {
            this.count++; // ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๋Š” ์™ธ๋ถ€ this๋ฅผ ์‚ฌ์šฉ!
            console.log(this.count);
        }, 1000);
    }
    
    // โœ… ํ•ด๊ฒฐ 2: self ํŒจํ„ด (ํด๋กœ์ € ํ™œ์šฉ)
    startFixed2() {
        const self = this; // this๋ฅผ ํด๋กœ์ €๋กœ ์บก์ฒ˜!
        setInterval(function() {
            self.count++;
            console.log(self.count);
        }, 1000);
    }
    
    // โœ… ํ•ด๊ฒฐ 3: bind ์‚ฌ์šฉ
    startFixed3() {
        setInterval(function() {
            this.count++;
            console.log(this.count);
        }.bind(this), 1000);
    }
}

// ๋ฒ„๊ทธ 3: ํด๋กœ์ €๊ฐ€ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ณ€์ˆ˜๋ฅผ ์บก์ฒ˜
function createAdders(numbers) {
    return numbers.map(function(num) {
        return function(x) {
            return num + x; // num์€ ๊ฐ ๋ฐ˜๋ณต์˜ ๊ฐ’์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์บก์ฒ˜!
        };
    });
}
// map์˜ ์ฝœ๋ฐฑ์€ ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ์Šค์ฝ”ํ”„๋ฅผ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์— ๊ดœ์ฐฎ์•„!
const adders = createAdders([1, 2, 3]);
console.log(adders[0](10)); // 11 โœ…
console.log(adders[1](10)); // 12 โœ…
console.log(adders[2](10)); // 13 โœ…
๐Ÿ’ก Chrome DevTools๋กœ ํด๋กœ์ € ๋””๋ฒ„๊น…ํ•˜๊ธฐ! Chrome DevTools์˜ Sources ํƒญ์—์„œ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ๋ฅผ ๊ฑธ๋ฉด,
์˜ค๋ฅธ์ชฝ Scope ํŒจ๋„์—์„œ Closure ์„น์…˜์„ ๋ณผ ์ˆ˜ ์žˆ์–ด.
๊ฑฐ๊ธฐ์„œ ํด๋กœ์ €๊ฐ€ ์–ด๋–ค ๋ณ€์ˆ˜๋ฅผ ์บก์ฒ˜ํ•˜๊ณ  ์žˆ๋Š”์ง€ ์ง์ ‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ”

17

ES6+ ํด๋ž˜์Šค vs ํด๋กœ์ € โ€” ์–ธ์ œ ๋ญ˜ ์จ์•ผ ํ•ด? ๐Ÿค”

ES6์—์„œ ํด๋ž˜์Šค ๋ฌธ๋ฒ•์ด ์ƒ๊ธฐ๋ฉด์„œ "ํด๋กœ์ € ๋Œ€์‹  ํด๋ž˜์Šค๋ฅผ ์“ฐ๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹ˆ์•ผ?" ํ•˜๋Š” ์งˆ๋ฌธ์ด ๋งŽ์•„์กŒ์–ด.
๋‘˜ ๋‹ค ์žฅ๋‹จ์ ์ด ์žˆ์œผ๋‹ˆ ๋น„๊ตํ•ด๋ณด์ž! โš–๏ธ

// ํด๋กœ์ € ๋ฐฉ์‹
function createCounter(initial = 0) {
    let count = initial;
    
    return {
        increment() { return ++count; },
        decrement() { return --count; },
        reset() { count = initial; return count; },
        getCount() { return count; }
    };
}

// ํด๋ž˜์Šค ๋ฐฉ์‹
class Counter {
    #count; // ES2022 private field
    #initial;
    
    constructor(initial = 0) {
        this.#count = initial;
        this.#initial = initial;
    }
    
    increment() { return ++this.#count; }
    decrement() { return --this.#count; }
    reset() { this.#count = this.#initial; return this.#count; }
    getCount() { return this.#count; }
}

// ์„ฑ๋Šฅ ๋น„๊ต
const closureCounter = createCounter(0);
const classCounter = new Counter(0);

// ํด๋กœ์ €: ๊ฐ ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ๋ฉ”์„œ๋“œ ๋ณต์‚ฌ๋ณธ ์ƒ์„ฑ
// ํด๋ž˜์Šค: ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์œผ๋กœ ๋ฉ”์„œ๋“œ ๊ณต์œ  (๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ !)

// ํ•˜์ง€๋งŒ ํด๋กœ์ €๋Š” ์ง„์งœ private!
// ํด๋ž˜์Šค์˜ #field๋Š” ์™„์ „ํ•œ private์ด์ง€๋งŒ
// ํด๋กœ์ €์˜ ๋ณ€์ˆ˜๋Š” ๋” ๊ฐ•๋ ฅํ•œ ์บก์Аํ™”๋ฅผ ์ œ๊ณตํ•ด
๋น„๊ต ํ•ญ๋ชฉ ํด๋กœ์ € ๋ฐฉ์‹ ํด๋ž˜์Šค ๋ฐฉ์‹
private ๋ฐ์ดํ„ฐ ์™„์ „ํ•œ private (์™ธ๋ถ€ ์ ‘๊ทผ ๋ถˆ๊ฐ€) #field๋กœ private ๊ฐ€๋Šฅ (ES2022+)
๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ๋ฉ”์„œ๋“œ ๋ณต์‚ฌ ํ”„๋กœํ† ํƒ€์ž… ๊ณต์œ ๋กœ ํšจ์œจ์ 
์ƒ์† ๋ณต์žกํ•จ (๋ฏน์Šค์ธ ํŒจํ„ด ํ•„์š”) extends๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ
๊ฐ€๋…์„ฑ ํ•จ์ˆ˜ํ˜• ์Šคํƒ€์ผ ์„ ํ˜ธ์ž์—๊ฒŒ ์ข‹์Œ OOP ๊ฒฝํ—˜์ž์—๊ฒŒ ์นœ์ˆ™ํ•จ
์œ ์—ฐ์„ฑ ํ•จ์ˆ˜ ํ•ฉ์„ฑ, ์ปค๋ง ๋“ฑ๊ณผ ์ž˜ ์–ด์šธ๋ฆผ ๊ตฌ์กฐํ™”๋œ ์ฝ”๋“œ์— ์ ํ•ฉ
๐ŸŽฏ ๊ฒฐ๋ก : ์ƒํ™ฉ์— ๋”ฐ๋ผ ์„ ํƒํ•ด! ํด๋กœ์ € ๋ฐฉ์‹: ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์Šคํƒ€์ผ, ๊ฐ„๋‹จํ•œ ์ƒํƒœ ๊ด€๋ฆฌ, ํŒฉํ† ๋ฆฌ ํŒจํ„ด
ํด๋ž˜์Šค ๋ฐฉ์‹: ๋ณต์žกํ•œ ๊ฐ์ฒด ๊ณ„์ธต, ์ƒ์†์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ํŒ€ ํ˜‘์—… (๊ฐ€๋…์„ฑ ์šฐ์„ )

18

์ข…ํ•ฉ ์‹ค์ „ ์˜ˆ์ œ โ€” ๋ฏธ๋‹ˆ ์ด๋ฒคํŠธ ๋ฒ„์Šค ๋งŒ๋“ค๊ธฐ ๐ŸšŒ

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋ชจ๋“  ๊ฒƒ์„ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ œ๋กœ ์“ธ ์ˆ˜ ์žˆ๋Š”
์ด๋ฒคํŠธ ๋ฒ„์Šค(Event Bus)๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž!
์ด๋ฒคํŠธ ๋ฒ„์Šค๋Š” ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ํ†ต์‹ ์— ๋งŽ์ด ์“ฐ์ด๋Š” ํŒจํ„ด์ด์•ผ. ๐ŸŽฏ

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์‚ฌ์šฉ์ž ์•Œ๋ฆผ, ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ ๊ฐ™์€ ๊ธฐ๋Šฅ์—
์ด๋Ÿฐ ์ด๋ฒคํŠธ ๋ฒ„์Šค ํŒจํ„ด์ด ํ™œ์šฉ๋  ์ˆ˜ ์žˆ์–ด!

// ํด๋กœ์ €๋ฅผ ํ™œ์šฉํ•œ ์™„์ „ํ•œ ์ด๋ฒคํŠธ ๋ฒ„์Šค ๊ตฌํ˜„
function createEventBus() {
    // private ์ƒํƒœ๋“ค
    const events = new Map();
    const onceEvents = new Map();
    let eventHistory = [];
    const maxHistorySize = 100;
    
    // private ํ•จ์ˆ˜: ํžˆ์Šคํ† ๋ฆฌ ์ถ”๊ฐ€
    function addToHistory(eventName, data) {
        eventHistory.push({
            event: eventName,
            data,
            timestamp: Date.now()
        });
        
        // ์ตœ๋Œ€ ํฌ๊ธฐ ์ดˆ๊ณผ ์‹œ ์˜ค๋ž˜๋œ ๊ฒƒ ์ œ๊ฑฐ
        if (eventHistory.length > maxHistorySize) {
            eventHistory.shift();
        }
    }
    
    // private ํ•จ์ˆ˜: ๋ฆฌ์Šค๋„ˆ ์‹คํ–‰
    async function executeListeners(listeners, data) {
        const results = [];
        for (const listener of listeners) {
            try {
                const result = await listener(data);
                results.push({ success: true, result });
            } catch (error) {
                results.push({ success: false, error: error.message });
                console.error('์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์˜ค๋ฅ˜:', error);
            }
        }
        return results;
    }
    
    return {
        // ์ด๋ฒคํŠธ ๊ตฌ๋…
        on(eventName, listener) {
            if (typeof listener !== 'function') {
                throw new TypeError('๋ฆฌ์Šค๋„ˆ๋Š” ํ•จ์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค!');
            }
            
            if (!events.has(eventName)) {
                events.set(eventName, new Set());
            }
            events.get(eventName).add(listener);
            
            // ๊ตฌ๋… ์ทจ์†Œ ํ•จ์ˆ˜ ๋ฐ˜ํ™˜ (ํด๋กœ์ €!)
            return function off() {
                const listeners = events.get(eventName);
                if (listeners) {
                    listeners.delete(listener);
                    if (listeners.size === 0) {
                        events.delete(eventName);
                    }
                }
            };
        },
        
        // ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋Š” ์ด๋ฒคํŠธ ๊ตฌ๋…
        once(eventName, listener) {
            if (!onceEvents.has(eventName)) {
                onceEvents.set(eventName, new Set());
            }
            onceEvents.get(eventName).add(listener);
            
            return function off() {
                const listeners = onceEvents.get(eventName);
                if (listeners) {
                    listeners.delete(listener);
                }
            };
        },
        
        // ์ด๋ฒคํŠธ ๋ฐœํ–‰
        async emit(eventName, data) {
            addToHistory(eventName, data);
            
            const results = [];
            
            // ์ผ๋ฐ˜ ๋ฆฌ์Šค๋„ˆ ์‹คํ–‰
            if (events.has(eventName)) {
                const listenerResults = await executeListeners(
                    events.get(eventName),
                    data
                );
                results.push(...listenerResults);
            }
            
            // once ๋ฆฌ์Šค๋„ˆ ์‹คํ–‰ ํ›„ ์ œ๊ฑฐ
            if (onceEvents.has(eventName)) {
                const onceListeners = onceEvents.get(eventName);
                const onceResults = await executeListeners(onceListeners, data);
                results.push(...onceResults);
                onceEvents.delete(eventName); // ์‹คํ–‰ ํ›„ ์ œ๊ฑฐ!
            }
            
            return results;
        },
        
        // ์ด๋ฒคํŠธ ํžˆ์Šคํ† ๋ฆฌ ์กฐํšŒ
        getHistory(eventName) {
            if (eventName) {
                return eventHistory.filter(h => h.event === eventName);
            }
            return [...eventHistory];
        },
        
        // ํŠน์ • ์ด๋ฒคํŠธ์˜ ๋ฆฌ์Šค๋„ˆ ์ˆ˜ ์กฐํšŒ
        listenerCount(eventName) {
            const regular = events.get(eventName)?.size ?? 0;
            const once = onceEvents.get(eventName)?.size ?? 0;
            return regular + once;
        },
        
        // ๋ชจ๋“  ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ
        removeAllListeners(eventName) {
            if (eventName) {
                events.delete(eventName);
                onceEvents.delete(eventName);
            } else {
                events.clear();
                onceEvents.clear();
            }
        },
        
        // ์ด๋ฒคํŠธ ๋ชฉ๋ก ์กฐํšŒ
        eventNames() {
            return [...new Set([
                ...events.keys(),
                ...onceEvents.keys()
            ])];
        }
    };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const bus = createEventBus();

// ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์ด๋ฒคํŠธ ๊ตฌ๋…
const unsubLogin = bus.on('user:login', async (user) => {
    console.log(`ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, ${user.name}๋‹˜!`);
    // await updateLastLoginTime(user.id);
});

// ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋Š” ์ดˆ๊ธฐํ™” ์ด๋ฒคํŠธ
bus.once('app:init', (config) => {
    console.log('์•ฑ ์ดˆ๊ธฐํ™”:', config);
});

// ์•Œ๋ฆผ ์ด๋ฒคํŠธ ๊ตฌ๋…
bus.on('notification', (notification) => {
    console.log(`์ƒˆ ์•Œ๋ฆผ: ${notification.message}`);
});

// ์ด๋ฒคํŠธ ๋ฐœํ–‰
await bus.emit('app:init', { version: '1.0.0', env: 'production' });
// "์•ฑ ์ดˆ๊ธฐํ™”: { version: '1.0.0', env: 'production' }"

await bus.emit('app:init', { version: '1.0.0' });
// ์•„๋ฌด๊ฒƒ๋„ ์ถœ๋ ฅ ์•ˆ ๋จ! (once๋ผ์„œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰)

await bus.emit('user:login', { id: 1, name: '๊น€์ฒ ์ˆ˜' });
// "ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, ๊น€์ฒ ์ˆ˜๋‹˜!"

await bus.emit('notification', { message: '์ƒˆ ๋ฉ”์‹œ์ง€๊ฐ€ ๋„์ฐฉํ–ˆ์Šต๋‹ˆ๋‹ค!' });
// "์ƒˆ ์•Œ๋ฆผ: ์ƒˆ ๋ฉ”์‹œ์ง€๊ฐ€ ๋„์ฐฉํ–ˆ์Šต๋‹ˆ๋‹ค!"

console.log(bus.getHistory('user:login'));
// [{ event: 'user:login', data: { id: 1, name: '๊น€์ฒ ์ˆ˜' }, timestamp: ... }]

// ๊ตฌ๋… ์ทจ์†Œ
unsubLogin();
await bus.emit('user:login', { id: 2, name: '์ด์˜ํฌ' });
// ์•„๋ฌด๊ฒƒ๋„ ์ถœ๋ ฅ ์•ˆ ๋จ! (๊ตฌ๋… ์ทจ์†Œ๋จ)

19

์ „์ฒด ์ •๋ฆฌ โ€” ํด๋กœ์ € ๋งˆ์Šคํ„ฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ โœ…

ํด๋กœ์ € ๋งˆ์Šคํ„ฐ ๋กœ๋“œ๋งต ๐Ÿ—บ๏ธ ๐Ÿ”‘ ํ•ต์‹ฌ ๊ฐœ๋… โœ… ์Šค์ฝ”ํ”„: ๋ณ€์ˆ˜์˜ ์œ ํšจ ๋ฒ”์œ„ โœ… ๋ ‰์‹œ์ปฌ ์Šค์ฝ”ํ”„: ์ •์˜ ์œ„์น˜ ๊ธฐ์ค€ โœ… ์Šค์ฝ”ํ”„ ์ฒด์ธ: ์•ˆโ†’๋ฐ– ๋ณ€์ˆ˜ ํƒ์ƒ‰ โœ… ํด๋กœ์ €: ์™ธ๋ถ€ ๋ณ€์ˆ˜ ๊ธฐ์–ตํ•˜๋Š” ํ•จ์ˆ˜ โœ… ์‹คํ–‰ ์ปจํ…์ŠคํŠธ: ์ฝ”๋“œ ์‹คํ–‰ ํ™˜๊ฒฝ โœ… ํ™˜๊ฒฝ ๋ ˆ์ฝ”๋“œ: ๋ณ€์ˆ˜ ์ €์žฅ์†Œ โœ… let/const vs var ์ฐจ์ด โœ… ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜๊ณผ ํด๋กœ์ € ๊ด€๊ณ„ ๐Ÿญ ํ™œ์šฉ ํŒจํ„ด โœ… ๋ฐ์ดํ„ฐ ์€๋‹‰ & ์บก์Аํ™” โœ… ๋ชจ๋“ˆ ํŒจํ„ด (IIFE) โœ… ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ โœ… ๋ถ€๋ถ„ ์ ์šฉ (Partial Application) โœ… ์ปค๋ง (Currying) โœ… ๋ฉ”๋ชจ์ด์ œ์ด์…˜ โœ… ๋””๋ฐ”์šด์Šค & ์Šค๋กœํ‹€ โœ… ์˜ต์ €๋ฒ„ ํŒจํ„ด โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ โš ๏ธ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€ โš ๏ธ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ •๋ฆฌ โš ๏ธ ๋ฃจํ”„์—์„œ var ์‚ฌ์šฉ ๊ธˆ์ง€ โš ๏ธ this ๋ฐ”์ธ๋”ฉ ํ˜ผ๋™ ์ฃผ์˜ โš ๏ธ ์ˆœํ™˜ ์ฐธ์กฐ ๋ฐฉ์ง€ โš ๏ธ ํด๋กœ์ € ๋‚จ์šฉ ์ž์ œ โš ๏ธ ์ˆœ์ˆ˜ ํ•จ์ˆ˜์—๋งŒ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ โš ๏ธ ํƒ€์ด๋จธ ์ •๋ฆฌ (clearInterval) ๐Ÿš€ ๊ณ ๊ธ‰ ํ™œ์šฉ ๐Ÿš€ ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ ๐Ÿš€ ์ƒํƒœ ๋จธ์‹  ๐Ÿš€ ์ด๋ฒคํŠธ ๋ฒ„์Šค ๐Ÿš€ ๋ฐ˜์‘ํ˜• ์ƒํƒœ ๊ด€๋ฆฌ ๐Ÿš€ ํ•จ์ˆ˜ ํ•ฉ์„ฑ (compose/pipe) ๐Ÿš€ ์ง€์—ฐ ์ดˆ๊ธฐํ™” ๐Ÿš€ ์ทจ์†Œ ๊ฐ€๋Šฅํ•œ ๋น„๋™๊ธฐ ๐Ÿš€ ์žฌ์‹œ๋„ ๋กœ์ง ํด๋กœ์ €๋ฅผ ๋งˆ์Šคํ„ฐํ•˜๋ฉด JavaScript์˜ ์ง„์งœ ํž˜์„ ์–ป๊ฒŒ ๋ผ! ๐Ÿ’ช

์ž, ์ด์ œ ํด๋กœ์ €์™€ ์Šค์ฝ”ํ”„ ์ฒด์ธ์— ๋Œ€ํ•ด ์ •๋ง ๊นŠ์ด ํŒŒ๊ณ ๋“ค์—ˆ์–ด! ๐ŸŽ‰
์ฒ˜์Œ์— "ํ•จ์ˆ˜๊ฐ€ ์ž์‹ ์ด ์„ ์–ธ๋œ ๋ ‰์‹œ์ปฌ ํ™˜๊ฒฝ์„ ๊ธฐ์–ตํ•˜๋Š” ๊ฒƒ"์ด๋ผ๋Š” ์ •์˜๊ฐ€ ์ถ”์ƒ์ ์œผ๋กœ ๋А๊ปด์กŒ๋‹ค๋ฉด,
์ด์ œ๋Š” ๊ทธ๊ฒŒ ์–ด๋–ค ์˜๋ฏธ์ธ์ง€ ์™„์ „ํžˆ ์ดํ•ดํ–ˆ์„ ๊ฑฐ์•ผ.

ํด๋กœ์ €๋Š” ๋‹จ์ˆœํžˆ "๋ณ€์ˆ˜๋ฅผ ๊ธฐ์–ตํ•˜๋Š” ํ•จ์ˆ˜"๊ฐ€ ์•„๋‹ˆ์•ผ.
๋ฐ์ดํ„ฐ ์€๋‹‰, ์ƒํƒœ ๊ด€๋ฆฌ, ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ, ์„ฑ๋Šฅ ์ตœ์ ํ™”๊นŒ์ง€
JavaScript ๊ฐœ๋ฐœ์˜ ๊ฑฐ์˜ ๋ชจ๋“  ์˜์—ญ์—์„œ ํ™œ์šฉ๋˜๋Š” ํ•ต์‹ฌ ๊ฐœ๋…์ด์•ผ.

์ด ๊ฐœ๋…๋“ค์„ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๋ฉด์„œ ์†์— ์ตํžˆ๋Š” ๊ฒŒ ์ค‘์š”ํ•ด.
์ฒ˜์Œ์—๋Š” ์–ด์ƒ‰ํ•˜๋”๋ผ๋„ ๊ณ„์† ์“ฐ๋‹ค ๋ณด๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํด๋กœ์ €๋ฅผ ํ™œ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์งœ๊ฒŒ ๋  ๊ฑฐ์•ผ! ๐Ÿ’ช


๐ŸŽฏ

๋งˆ๋ฌด๋ฆฌ โ€” ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ

ํด๋กœ์ €์™€ ์Šค์ฝ”ํ”„ ์ฒด์ธ์„ ์ดํ•ดํ–ˆ๋‹ค๋ฉด, ์ด์ œ ์ด๋Ÿฐ ๊ฒƒ๋“ค๋„ ๊ณต๋ถ€ํ•ด๋ณด๋ฉด ์ข‹์•„:

๐Ÿ“š ๋‹ค์Œ ํ•™์Šต ์ถ”์ฒœ ์ฃผ์ œ๋“ค

1. ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ โ€” ํด๋กœ์ €์™€ ํ•จ๊ป˜ JavaScript OOP์˜ ํ•ต์‹ฌ
2. ์ œ๋„ˆ๋ ˆ์ดํ„ฐ์™€ ์ดํ„ฐ๋ ˆ์ดํ„ฐ โ€” ํด๋กœ์ €์™€ ๊ฒฐํ•ฉํ•˜๋ฉด ๊ฐ•๋ ฅํ•œ ์ƒํƒœ ๊ด€๋ฆฌ
3. Proxy์™€ Reflect โ€” ํด๋กœ์ €๋ฅผ ๋„˜์–ด์„  ๋ฉ”ํƒ€ํ”„๋กœ๊ทธ๋ž˜๋ฐ
4. WeakRef์™€ FinalizationRegistry โ€” ํด๋กœ์ € ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์˜ ์ตœ์‹  ๊ธฐ๋ฒ•
5. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ ํŒจํ„ด โ€” ์ปค๋ง, ๋ชจ๋‚˜๋“œ, ํŽ‘ํ„ฐ ๋“ฑ

"ํด๋กœ์ €๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์€ JavaScript๋ฅผ ์ง„์ •์œผ๋กœ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
ํด๋กœ์ €๋Š” ์–ธ์–ด์˜ ๋ฒ„๊ทธ๊ฐ€ ์•„๋‹ˆ๋ผ, ์˜๋„์ ์œผ๋กœ ์„ค๊ณ„๋œ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์ด๋‹ค."
โ€” Kyle Simpson, "You Don't Know JS" ์ €์ž

JavaScript ๊ฐœ๋ฐœ ์‹ค๋ ฅ์„ ๋” ํ‚ค์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด,
์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์—์„œ JavaScript ์ „๋ฌธ๊ฐ€๋“ค์˜ ๊ฐ•์˜๋‚˜ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์„œ๋น„์Šค๋ฅผ ์ฐพ์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ.
๋‹ค์–‘ํ•œ ๊ฐœ๋ฐœ์ž๋“ค์˜ ๊ฒฝํ—˜๊ณผ ๋…ธํ•˜์šฐ๋ฅผ ์ง์ ‘ ๋ฐฐ์šธ ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐ŸŒŸ

์˜ค๋Š˜ ๋ฐฐ์šด ํด๋กœ์ €์™€ ์Šค์ฝ”ํ”„ ์ฒด์ธ, ๊ผญ ์ง์ ‘ ์ฝ”๋“œ๋ฅผ ์งœ๋ณด๋ฉด์„œ ์ตํ˜€๋ด.
์ด๋ก ๋งŒ ์•Œ๊ณ  ์žˆ์œผ๋ฉด ๊ธˆ๋ฐฉ ์žŠ์–ด๋ฒ„๋ฆฌ์ง€๋งŒ, ์†์œผ๋กœ ์ง์ ‘ ์จ๋ณด๋ฉด ์ ˆ๋Œ€ ์•ˆ ์žŠ์–ด! โœ๏ธ

ํ™”์ดํŒ…! ๋„ˆ์˜ JavaScript ์‹ค๋ ฅ์ด ์˜ค๋Š˜๋ณด๋‹ค ํ›จ์”ฌ ์„ฑ์žฅํ–ˆ์„ ๊ฑฐ์•ผ! ๐Ÿš€๐ŸŽ‰

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.