Ver3.5 ๐ ํด๋ก์ ์ ์ค์ฝํ ์ฒด์ธ์ ํ์ฉํ๊ณ ๊ธ JavaScript ๊ฐ๋ฐ ๊ธฐ๋ฒ

๐ ํด๋ก์ ์ ์ค์ฝํ ์ฒด์ธ์ ํ์ฉํ
๊ณ ๊ธ JavaScript ๊ฐ๋ฐ ๊ธฐ๋ฒ
๐ ์ค์ฝํ์ ๋น๋ฐ๋ถํฐ ํด๋ก์ ๋ง์คํฐ๊น์ง โ ์ง์ง JS ๊ฐ๋ฐ์๋ก ๋ ๋ฒจ์ ํ๋ ์ฌ์
์ผ, ์์งํ ๋งํด๋ณผ๊ฒ. ๐
JavaScript๋ฅผ ์ฒ์ ๋ฐฐ์ธ ๋ ํด๋ก์ (Closure)๋ผ๋ ๋จ์ด ๋ค์ผ๋ฉด ๋ญ๊ฐ ๋ฌด์ญ์ง ์์์ด?
"ํด๋ก์ ๊ฐ ๋ญ๋ฐ์?" ํ๊ณ ๋ฌผ์ด๋ณด๋ฉด ๋์์ค๋ ๋๋ต์ด ํญ์ ๋ญ๊ฐ ์ถ์์ ์ด๊ณ ์ฒ ํ์ ์ด์์.
"ํจ์๊ฐ ์์ ์ด ์ ์ธ๋ ๋ ์์ปฌ ํ๊ฒฝ์ ๊ธฐ์ตํ๋ ๊ฒ์
๋๋ค." โ ์? ๐ค
๊ทผ๋ฐ ์ฌ์ค ํด๋ก์ ๋ ํ๋ฒ ์ ๋๋ก ์ดํดํ๋ฉด JavaScript์์ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๋ฌด๊ธฐ ์ค ํ๋์ผ.
๊ทธ๋ฆฌ๊ณ ์ค์ฝํ ์ฒด์ธ(Scope Chain)์ด๋ ๊ฐ์ด ์ดํดํ๋ฉด, ์ฝ๋๊ฐ ์ ๊ทธ๋ ๊ฒ ๋์ํ๋์ง
๋์ ํคํ ๋ณด์ด๊ธฐ ์์ํด. ๋ง์น X-ray ์๊ฒฝ์ ์ด ๊ฒ์ฒ๋ผ! ๐ถ๏ธ
์ด ๊ธ์์๋ ํด๋ก์ ์ ์ค์ฝํ ์ฒด์ธ์ ๊ฐ๋
๋ถํฐ ์์ํด์,
์ค์ ๋ก ๊ณ ๊ธ JavaScript ๊ฐ๋ฐ์์ ์ด๋ป๊ฒ ํ์ฉํ๋์ง ์์ฃผ ๊น์์ด ํ๊ณ ๋ค ๊ฑฐ์ผ.
๋ชจ๋ ํจํด, ๋ฉ๋ชจ์ด์ ์ด์
, ์ปค๋ง, ํฉํ ๋ฆฌ ํจ์, ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ต์ ํ๊น์ง โ ์ ๋ถ ๋ค ๋ค๋ฃฐ ๊ฑฐ๋๊น ๊ธฐ๋ํด! ๐ฏ
์ค์ฝํ(Scope)๋ ๋ญ๋ฐ? โ ๋ณ์์ '์ ํจ ๋ฒ์' ์ด์ผ๊ธฐ
ํด๋ก์ ๋ฅผ ์ดํดํ๋ ค๋ฉด ๋จผ์ ์ค์ฝํ(Scope)๋ฅผ ์ ๋๋ก ์์์ผ ํด.
์ค์ฝํ๋ ์ฝ๊ฒ ๋งํ๋ฉด "๋ณ์๊ฐ ์ด ์ ์๋ ์ง"์ด์ผ. ๐
์ด๋ค ๋ณ์๊ฐ ์ด๋์ ์ ๊ทผ ๊ฐ๋ฅํ๊ณ , ์ด๋์๋ ์ ๊ทผ ๋ถ๊ฐ๋ฅํ์ง๋ฅผ ๊ฒฐ์ ํ๋ ๊ท์น์ด์ง.
JavaScript์๋ ํฌ๊ฒ ์ธ ๊ฐ์ง ์ค์ฝํ๊ฐ ์์ด:
์, ์ด์ ๊ฐ๊ฐ ์ข ๋ ์์ธํ ์ดํด๋ณผ๊ฒ.
์ฝ๋๋ก ์ง์ ๋ณด๋ ๊ฒ ์ ์ผ ๋น ๋ฅด์์? ๐
// ๐ ์ ์ญ ์ค์ฝํ ์์
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๋ ๋ธ๋ก ์ค์ฝํ๋ฅผ ๋ฌด์ํ๊ณ ํจ์ ์ค์ฝํ๋ง ๋ฐ๋ผ๊ฐ. ๊ทธ๋์ if๋ฌธ์ด๋ for๋ฌธ ์์์ ์ ์ธํ var๊ฐ ๋ฐ์์๋ ์ ๊ทผ๋๋ ํฉ๋นํ ์ํฉ์ด ์๊ฒจ. ์ด๊ฒ ์๋ง์ ๋ฒ๊ทธ์ ์์ธ์ด์๊ธฐ ๋๋ฌธ์, ํ๋ JavaScript์์๋ let๊ณผ const๋ฅผ ์ฐ๋ ๊ฒ ์ ์์ด์ผ!
๋ ์์ปฌ ์ค์ฝํ(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 ํจ์๊ฐ ์ด๋ฏธ ์คํ์ ๋ง์ณค๋๋ฐ๋ ๋ง์ด์ผ!
์ด๊ฒ ๋ฐ๋ก ํด๋ก์ ์ ๋ง๋ฒ์ด์ผ. ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ํด๋ก์ ๋ฅผ ํํค์ณ๋ณด์! ๐
์ค์ฝํ ์ฒด์ธ(Scope Chain) โ ๋ณ์๋ฅผ ์ฐพ์ ๋ ๋๋ ์ฌํ ๐บ๏ธ
์ค์ฝํ ์ฒด์ธ์ JavaScript ์์ง์ด ๋ณ์๋ฅผ ์ฐพ๋ ๋ฐฉ๋ฒ์ด์ผ.
ํจ์ ์์์ ๋ณ์๋ฅผ ์ฐธ์กฐํ๋ฉด, JS ์์ง์ ์ด๋ ๊ฒ ์๊ฐํด:
"์ด ๋ณ์๊ฐ ํ์ฌ ์ค์ฝํ์ ์๋? ์์ผ๋ฉด ๋ฐ๊นฅ ์ค์ฝํ๋ฅผ ๋ด. ๊ฑฐ๊ธฐ๋ ์์ผ๋ฉด ๋ ๋ฐ๊นฅ์ ๋ด. ์ ์ญ๊น์ง ๊ฐ๋ ์์ผ๋ฉด ์๋ฌ!"
์ด๋ ๊ฒ ์์ชฝ์์ ๋ฐ๊นฅ์ชฝ์ผ๋ก ๋ณ์๋ฅผ ์ฐพ์๊ฐ๋ ์ฐ๊ฒฐ ๊ณ ๋ฆฌ๊ฐ ๋ฐ๋ก ์ค์ฝํ ์ฒด์ธ์ด์ผ! โ๏ธ
// ์ค์ฝํ ์ฒด์ธ ์ค์ ์์
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; ์ด๋ฐ ์์ผ๋ก ์์ฃผ ์ฐ๋ ์ ์ญ ๊ฐ์ฒด๋ฅผ ์ง์ญ ๋ณ์์ ๋ด์๋๋ ๊ฑฐ์ผ.
ํด๋ก์ (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๋ฅผ ๊ฐ์ง๊ณ ์์ด.
์ด๊ฒ ํด๋ก์ ์ ํต์ฌ์ด์ผ!
์ด์ ์ด๊ฑธ ํ์ฉํ ๊ณ ๊ธ ํจํด๋ค์ ํ๋์ฉ ์ดํด๋ณด์. ์ง์ง ์ฌ๋ฏธ์๋ ๋ถ๋ถ์ด ์์๋ผ! ๐
์คํ ์ปจํ ์คํธ์ ํ๊ฒฝ ๋ ์ฝ๋ โ ํด๋ก์ ์ ๋ด๋ถ ๋์ ์๋ฆฌ ๐ฌ
ํด๋ก์ ๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ ๊น์ด ์ดํดํ๋ ค๋ฉด
์คํ ์ปจํ
์คํธ(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๋ฅผ ์ฐพ์๋!
ํด๋ก์ ํ์ฉ ํจํด โ โ ๋ฐ์ดํฐ ์๋๊ณผ ์บก์ํ ๐
ํด๋ก์ ์ ๊ฐ์ฅ ๊ฐ๋ ฅํ ํ์ฉ ์ค ํ๋๊ฐ ๋ฐ๋ก ๋ฐ์ดํฐ ์๋(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 ๐
ํด๋ก์ ํ์ฉ ํจํด โก โ ํฉํ ๋ฆฌ ํจ์์ ๋ถ๋ถ ์ ์ฉ ๐ญ
ํด๋ก์ ๋ฅผ ์ด์ฉํ๋ฉด ํฉํ ๋ฆฌ ํจ์(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...: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์คํจ
ํด๋ก์ ํ์ฉ ํจํด โข โ ์ปค๋ง(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
ํด๋ก์ ํ์ฉ ํจํด โฃ โ ๋ฉ๋ชจ์ด์ ์ด์ (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;
};
}
๊ฐ์ ์ ๋ ฅ์ ํญ์ ๊ฐ์ ์ถ๋ ฅ์ ๋ด๋ ํจ์์ฌ์ผ ์บ์ฑ์ด ์๋ฏธ ์์ด.
๋ถ์์ฉ(Side Effect)์ด ์๋ ํจ์์ ๋ฉ๋ชจ์ด์ ์ด์ ์ ์ ์ฉํ๋ฉด ์์์น ๋ชปํ ๋ฒ๊ทธ๊ฐ ์๊ธธ ์ ์์ด!
์ด๋ฒคํธ ํธ๋ค๋ฌ์ ํด๋ก์ โ ์ค์ 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);
}
});
}
๊ณ ๊ธ ํจํด โ ํด๋ก์ ๋ก ๊ตฌํํ๋ ์ต์ ๋ฒ ํจํด ๐๏ธ
์ต์ ๋ฒ ํจํด(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" (์ ์ฒด ๋ฆฌ์ค๋๋ง ํธ์ถ)
ํด๋ก์ ์ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ โ 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));
์ค๋กํ: ์คํฌ๋กค ์ด๋ฒคํธ, ๋ง์ฐ์ค ์ด๋์ฒ๋ผ "์ผ์ ๊ฐ๊ฒฉ์ผ๋ก ์ฒ๋ฆฌ"๊ฐ ํ์ํ ๋
ํด๋ก์ ์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ โ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์งํ๊ธฐ ๐๏ธ
ํด๋ก์ ๋ ๊ฐ๋ ฅํ์ง๋ง, ์๋ชป ์ฌ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์(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;
}
}
2. ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฏธ์ ๊ฑฐ: DOM ์์๊ฐ ์ ๊ฑฐ๋์ด๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ๋จ์์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์
3. ํ์ด๋จธ ๋ฏธ์ ๋ฆฌ: setInterval์ ์ฝ๋ฐฑ์ด ํด๋ก์ ๋ฅผ ํตํด ํฐ ๋ฐ์ดํฐ๋ฅผ ์ฐธ์กฐํ๋ฉด ์ํ
4. ์ํ ์ฐธ์กฐ: ํด๋ก์ ๊ฐ ์๋ก๋ฅผ ์ฐธ์กฐํ๋ ๊ฒฝ์ฐ
์ค์ ํ๋ก์ ํธ ํจํด โ ํด๋ก์ ๋ก ๋ง๋๋ ๋ฏธ๋ ํ๋ ์์ํฌ ๐๏ธ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๊ฒ๋ค์ ์ข
ํฉํด์ ์ค์ ํ๋ก์ ํธ์์ ์ธ ์ ์๋ ํจํด๋ค์ ๋ง๋ค์ด๋ณด์!
์ฌ๋ฅ๋ท(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('๋ฐฐ์ก์์');
// "๋ฐฐ์ก ์์!"
// "์ํ ๋ณ๊ฒฝ: ๊ฒฐ์ ๋จ โ ๋ฐฐ์ก์ค (์ด๋ฒคํธ: ๋ฐฐ์ก์์)"
ํด๋ก์ ์ฑ๋ฅ ์ต์ ํ ํ โก
ํด๋ก์ ๋ฅผ ์ ์ฐ๋ฉด ์ฑ๋ฅ๋ ์ฌ๋ฆด ์ ์์ด!
๋ช ๊ฐ์ง ์ค์ํ ์ต์ ํ ํ์ ์์๋ณด์. ๐
| ์ต์ ํ ๊ธฐ๋ฒ | ์ค๋ช | ํจ๊ณผ |
|---|---|---|
| ์ ์ญ ๋ณ์ ์ง์ญํ | ์์ฃผ ์ฐ๋ ์ ์ญ ๋ณ์๋ฅผ ์ง์ญ ๋ณ์์ ์บ์ฑ | ์ค์ฝํ ์ฒด์ธ ํ์ ๊ฐ์ |
| ๋ฉ๋ชจ์ด์ ์ด์ | ๊ณ์ฐ ๊ฒฐ๊ณผ๋ฅผ ํด๋ก์ ๋ก ์บ์ฑ | ์ค๋ณต ๊ณ์ฐ ์ ๊ฑฐ |
| ์ง์ฐ ์ด๊ธฐํ | ์ฒ์ ํ์ํ ๋๋ง ์ด๊ธฐํ | ๋ถํ์ํ ์ด๊ธฐ ๋น์ฉ ์ ๊ฑฐ |
| ํด๋ก์ ์ต์ํ | ํ์ํ ๋ณ์๋ง ํด๋ก์ ์ ํฌํจ | ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ฐ์ |
// ์ฑ๋ฅ ์ต์ ํ ์์ 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 }
ํด๋ก์ ๋๋ฒ๊น โ ๋ฌธ์ ์ฐพ๊ณ ํด๊ฒฐํ๊ธฐ ๐
ํด๋ก์ ๊ด๋ จ ๋ฒ๊ทธ๋ ์ฒ์์ ์ ๋ง ์ฐพ๊ธฐ ์ด๋ ค์.
ํ์ง๋ง ํจํด์ ์๋ฉด ๊ธ๋ฐฉ ์ฐพ์ ์ ์์ด! ๐
๊ฐ์ฅ ํํ ํด๋ก์ ๋ฒ๊ทธ๋ค๊ณผ ํด๊ฒฐ์ฑ
์ ์ดํด๋ณด์.
// ๋ฒ๊ทธ 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 โ
์ค๋ฅธ์ชฝ Scope ํจ๋์์ Closure ์น์ ์ ๋ณผ ์ ์์ด.
๊ฑฐ๊ธฐ์ ํด๋ก์ ๊ฐ ์ด๋ค ๋ณ์๋ฅผ ์บก์ฒํ๊ณ ์๋์ง ์ง์ ํ์ธํ ์ ์์ด! ๐
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 ๊ฒฝํ์์๊ฒ ์น์ํจ |
| ์ ์ฐ์ฑ | ํจ์ ํฉ์ฑ, ์ปค๋ง ๋ฑ๊ณผ ์ ์ด์ธ๋ฆผ | ๊ตฌ์กฐํ๋ ์ฝ๋์ ์ ํฉ |
ํด๋์ค ๋ฐฉ์: ๋ณต์กํ ๊ฐ์ฒด ๊ณ์ธต, ์์์ด ํ์ํ ๊ฒฝ์ฐ, ํ ํ์ (๊ฐ๋ ์ฑ ์ฐ์ )
์ข ํฉ ์ค์ ์์ โ ๋ฏธ๋ ์ด๋ฒคํธ ๋ฒ์ค ๋ง๋ค๊ธฐ ๐
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ชจ๋ ๊ฒ์ ์ข
ํฉํด์ ์ค์ ๋ก ์ธ ์ ์๋
์ด๋ฒคํธ ๋ฒ์ค(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: '์ด์ํฌ' });
// ์๋ฌด๊ฒ๋ ์ถ๋ ฅ ์ ๋จ! (๊ตฌ๋
์ทจ์๋จ)
์ ์ฒด ์ ๋ฆฌ โ ํด๋ก์ ๋ง์คํฐ ์ฒดํฌ๋ฆฌ์คํธ โ
์, ์ด์ ํด๋ก์ ์ ์ค์ฝํ ์ฒด์ธ์ ๋ํด ์ ๋ง ๊น์ด ํ๊ณ ๋ค์์ด! ๐
์ฒ์์ "ํจ์๊ฐ ์์ ์ด ์ ์ธ๋ ๋ ์์ปฌ ํ๊ฒฝ์ ๊ธฐ์ตํ๋ ๊ฒ"์ด๋ผ๋ ์ ์๊ฐ ์ถ์์ ์ผ๋ก ๋๊ปด์ก๋ค๋ฉด,
์ด์ ๋ ๊ทธ๊ฒ ์ด๋ค ์๋ฏธ์ธ์ง ์์ ํ ์ดํดํ์ ๊ฑฐ์ผ.
ํด๋ก์ ๋ ๋จ์ํ "๋ณ์๋ฅผ ๊ธฐ์ตํ๋ ํจ์"๊ฐ ์๋์ผ.
๋ฐ์ดํฐ ์๋, ์ํ ๊ด๋ฆฌ, ํจ์ํ ํ๋ก๊ทธ๋๋ฐ, ์ฑ๋ฅ ์ต์ ํ๊น์ง
JavaScript ๊ฐ๋ฐ์ ๊ฑฐ์ ๋ชจ๋ ์์ญ์์ ํ์ฉ๋๋ ํต์ฌ ๊ฐ๋
์ด์ผ.
์ด ๊ฐ๋
๋ค์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด๋ฉด์ ์์ ์ตํ๋ ๊ฒ ์ค์ํด.
์ฒ์์๋ ์ด์ํ๋๋ผ๋ ๊ณ์ ์ฐ๋ค ๋ณด๋ฉด ์์ฐ์ค๋ฝ๊ฒ ํด๋ก์ ๋ฅผ ํ์ฉํ๋ ์ฝ๋๋ฅผ ์ง๊ฒ ๋ ๊ฑฐ์ผ! ๐ช
๋ง๋ฌด๋ฆฌ โ ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ๊ธฐ
ํด๋ก์ ์ ์ค์ฝํ ์ฒด์ธ์ ์ดํดํ๋ค๋ฉด, ์ด์ ์ด๋ฐ ๊ฒ๋ค๋ ๊ณต๋ถํด๋ณด๋ฉด ์ข์:
๐ ๋ค์ ํ์ต ์ถ์ฒ ์ฃผ์ ๋ค
1. ํ๋กํ ํ์
์ฒด์ธ โ ํด๋ก์ ์ ํจ๊ป JavaScript OOP์ ํต์ฌ
2. ์ ๋๋ ์ดํฐ์ ์ดํฐ๋ ์ดํฐ โ ํด๋ก์ ์ ๊ฒฐํฉํ๋ฉด ๊ฐ๋ ฅํ ์ํ ๊ด๋ฆฌ
3. Proxy์ Reflect โ ํด๋ก์ ๋ฅผ ๋์ด์ ๋ฉํํ๋ก๊ทธ๋๋ฐ
4. WeakRef์ FinalizationRegistry โ ํด๋ก์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ์ต์ ๊ธฐ๋ฒ
5. ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ํจํด โ ์ปค๋ง, ๋ชจ๋๋, ํํฐ ๋ฑ
ํด๋ก์ ๋ ์ธ์ด์ ๋ฒ๊ทธ๊ฐ ์๋๋ผ, ์๋์ ์ผ๋ก ์ค๊ณ๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ด๋ค."
โ Kyle Simpson, "You Don't Know JS" ์ ์
JavaScript ๊ฐ๋ฐ ์ค๋ ฅ์ ๋ ํค์ฐ๊ณ ์ถ๋ค๋ฉด,
์ฌ๋ฅ๋ท(https://www.jaenung.net)์์ JavaScript ์ ๋ฌธ๊ฐ๋ค์ ๊ฐ์๋ ์ฝ๋ ๋ฆฌ๋ทฐ ์๋น์ค๋ฅผ ์ฐพ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ.
๋ค์ํ ๊ฐ๋ฐ์๋ค์ ๊ฒฝํ๊ณผ ๋
ธํ์ฐ๋ฅผ ์ง์ ๋ฐฐ์ธ ์ ์๊ฑฐ๋ ! ๐
์ค๋ ๋ฐฐ์ด ํด๋ก์ ์ ์ค์ฝํ ์ฒด์ธ, ๊ผญ ์ง์ ์ฝ๋๋ฅผ ์ง๋ณด๋ฉด์ ์ตํ๋ด.
์ด๋ก ๋ง ์๊ณ ์์ผ๋ฉด ๊ธ๋ฐฉ ์์ด๋ฒ๋ฆฌ์ง๋ง, ์์ผ๋ก ์ง์ ์จ๋ณด๋ฉด ์ ๋ ์ ์์ด! โ๏ธ
ํ์ดํ
! ๋์ JavaScript ์ค๋ ฅ์ด ์ค๋๋ณด๋ค ํจ์ฌ ์ฑ์ฅํ์ ๊ฑฐ์ผ! ๐๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.