Javascript 'Closure'
What is Closure ? ๐ก
ํด๋ก์ ๋ ํจ์์ ํจ์๊ฐ ์ ์ธ๋ ์ดํ์ ํ๊ฒฝ์ ์กฐํฉ์ด๋ค. by MDN
์ญ์ MDN ๋ง์ ํ๋ฒ์ ์์๋ฃ๊ธฐ ์ด๋ ค์ฐ๋ฏ๋ก ํ์ด์ ์ ๋ฆฌํด๋ณด์! ๐
ํด๋ก์ ธ๋ ๊ณผ์ฐ ๋ฌด์์ผ๊น??
๋จผ์ ์๋ฐ์คํฌ๋ฆฝํธ Deepdive ์ฑ ์ ์๋ ์์ ๋ก ๋ณด๊ฒ ๋ค.
const outer = () => {
const out = "outer!"; // 1. outer ํจ์ ์์ ์ง์ญ๋ณ์ out ์ ์ธ
const inner = () => {
console.log(out); // 2. ๋ฐ๊นฅ์ out์ ์ฐธ์กฐํด console.log ์ถ๋ ฅ
};
return inner; // 3. ๋ฐ๊นฅ์ out์ ์ฐธ์กฐํด console.log๋ฅผ ์ถ๋ ฅํ๋ ํจ์๋ฅผ ๋ฐํ
};
const foo = outer(); // 4. outerํจ์ ํธ์ถ -> ๋ณ์ foo์ innerํจ์์ ์ฃผ์๊ฐ์ด ์ ์ฅ๋จ
foo(); // 5. 'outer!'
์ด ์์ค์ฝ๋๋ค์ ํด๋ก์ ธ์ ํ์คํ ์์์ด๋ค.
์ฆ ํด๋ก์ ธ๋ ์ด๋ค ํจ์(outer) ๋ด๋ถ์ ์ ์ธ๋ ํจ์(inner)๊ฐ ๋ฐ๊นฅ ํจ์(outer)์ ์ง์ญ๋ณ์(outerVariable)๋ฅผ ์ฐธ์กฐํ๋ ๊ฒ์ด ํจ์(outer)๊ฐ ์ข ๋ฃ๋ ์ดํ์๋ ๊ณ์ ์ ์ง๋๋ ํ์์ ๋งํฉ๋๋ค. outer ํจ์์ ๋ผ์ดํ์ฌ์ดํด์ด inner ํจ์๋ณด๋ค ์งง์๋ ์ด์ผ๊ธฐ์ด๋ค.
โ ๏ธ ๋ค๋ง !! ์ ์ญ์ปจํ ์คํธ์์ ์ฐธ์กฐ๋๋ outer ํจ์๋ ์ข ๋ฃ๋๋ ๊ฒ์ด ๋ง์ผ๋ ์ฌ๋ผ์ง์ง์๊ณ ์กด์ฌ๋ ํ๋ค. ๋ค๋ง ๋ฉ๋ชจ๋ฆฌ ์ด๋๊ฐ์์ ์กด์ฌํ ๋ฟ ๊ฐ๋น์ง์ปฌ๋ ํฐ์ ์ํด์ ์์ ํ ์๋ฉธ๋๋ ๊ฒ์ด ์๋๋ค. ์ ์ด๋ inner ํจ์๊ฐ ์์ด์ง ๋๊น์ง๋ outer ํจ์๊ฐ ์กด์ฌํ๊ฒ๋๋ค.
Why Closure exist ? ๐จ
Scope
function scopeA() {
const a = "ํธ๋์ด";
function scopeB() {
const a = "์ฌ์";
console.log(a);
}
scopeB(); // ์ฌ์
console.log(a);
}
scopeA(); // ํธ๋์ด
console.log(a); // reference error
scopeA๋ฅผ ํธ์ถ ํ์ ๋ ์ฌ์๊ฐ ์๋ ํธ๋์ด๊ฐ ์ถ๋ ฅ๋์๋ค. ์ด๊ฒ์ scopeB ํจ์์ ๋ณ์ ์ ์ธ ๋ฐ ํ ๋น ๊ณผ์ ์ด scopeA์ ๋์์ ์ํฅ์ ์ฃผ์ง ์์๋ค๋ ๊ฒ์ด๋ค. ์ฆ, scopeA์ scopeB๊ฐ ๊ณ ์ ํ ์ค์ฝํ๋ฅผ ๊ฐ์ง๊ณ ์๋ค๊ณ ๋งํ ์ ์์ต๋๋ค.
๋ชจ๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ ์ฝ๋๋ฅผ ํ ์ค์ฉ ์ฝ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ๊ณ์ฐ(์์ผ๋ก๋ ํ๊ฐํ๋ค๊ณ ํ๊ฒ ์ต๋๋ค.)ํด ๋ฉ๋ชจ๋ฆฌ์ ๊ณ์ฐ๋ ๊ฐ์ ์ ์ฅ(const a = โํธ๋์ดโ)ํ๊ฑฐ๋, ํน์ ํ ๋์์(console.log(a))ํ๋ค.
์๋ฐ์คํฌ๋ฆฝํธ๋ ํ๋ก๊ทธ๋จ์ ํ๊ฐํ๊ธฐ ์ ๊ณผ ํจ์๋ฅผ ํ๊ฐํ๊ธฐ ์ ์ ๋ณ์ ์ ์ธ๊ณผ ํจ์ ์ ์ธ ์ ๋ณด๋ฅผ ๋ฏธ๋ฆฌ ํ ๋ฒ ์ญ ํ์ด์ ์์งํ๋ค. (์คํ์ปจํ ์คํธ์ Environment Record๋ฅผ ์์งํ๋ ๊ณผ์ )
ํ๋ก๊ทธ๋จ์ ์์ํ๊ธฐ ์ ์ scopeAํจ์ ์ ์ธ ์ ๋ณด๋ฅผ ์์งํ๊ณ ํ ์ค์ฉ ํ๊ฐ๋ฅผ ํ๋ค๊ฐ, scopeA๊ฐ ํธ์ถ๋๋ ์์ ์ scopeA ๋ด๋ถ์ (๋ฌธ์์ด ํธ๋์ด๊ฐ ํ ๋น๋๋) a ์ scopeB์ ์ ๋ณด๋ฅผ ์์งํ๋ ๊ฒ์ด๋ค. ์ด๋ ๊ฒ ์ ๋ณด๋ฅผ ์์งํ๊ณ ๋ค์ ์ฒ์์ผ๋ก ๋์์ ํ ์ค์ฉ ํ๊ฐ๋ฅผ ์์ํ๋๋ฐ, ํ๊ฐํ๋ ์ฝ๋์ค์์ a์ ๊ฐ์ด ํ ๋น๋๊ฑฐ๋, console.log(a)๊ณผ ๊ฐ์ด ์ฌ์ฉ๋ ๋, ๋ฏธ๋ฆฌ ์์งํด๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ ๊ฐ์ ์๋ก ์ ์ฅํ๊ณ , ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํจ์ ํธ์ถ ์ ์ ๋ด๊ธด ์ ๋ณด๋ก๋ ๋ค๋ฅธ ํจ์ ๋ด๋ถ์ ๋ณ์๋ฅผ ์ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค ๊ทธ๋์ ๊ฐ ํจ์๋ ๊ณ ์ ํ ์ค์ฝํ๋ฅผ ๊ฐ์ง๊ฒ ๋๋ค. (es6์์๋ while, if, for๋ฌธ ๊ฐ์ ํจ์๊ฐ ์๋ ๋ธ๋ก๋ฌธ์์๋ ์๋ก ์ค์ฝํ๋ฅผ ๋ง๋ฆ.) ์ด๋ ๊ฒ ๋ง๋ค์ด์ง ์ค์ฝํ๋ ํจ์๊ฐ ์ข ๋ฃ๋๋ฉด์ ์ฌ๋ผ์ง๊ฒ ๋๋ค.
Scope Chain
์ค์ฝํ ๋ด๋ถ์์ ์ ์ธ๋ ํจ์์ ์๋ณ์๋ ์์ ์์ง๋ง ์ค์ฝํ ๋ฐ๊นฅ์ ์๋ณ์๋ ์ ์ ์๋ค.
function outer() {
const outer = "outer!";
function inner() {
console.log(outer);
}
inner(); // outer!
}
์๋ฐ์คํฌ๋ฆฝํธ๋ ์ค์ฝํ ๋ด์ ์ฐธ์กฐํ ์ ์๋ ๋ณ์๋ ํจ์๊ฐ ์กด์ฌํ์ง ์์ผ๋ฉด ๋ฐ๊นฅ์ ์ค์ฝํ์์ ์๋ณ์ ์ ๋ณด๋ฅผ ์ฐพ๋๋ค.
์ด๊ฒ์ด ๊ฐ๋ฅํ ์ด์ ๋, ์์ ๋งํ๋ ํจ์ ํ๊ฐ ์ด์ ์ ์ญ ํ๋ ๊ณผ์ (๋ณ์ ์ ์ธ, ํจ์์ ์ธ ์์ง) ์ด์ธ์ ๋ฐ๊นฅ ์ค์ฝํ์ ๋ํ ์ ๋ณด๋ฅผ ์์งํ๋ ๊ณผ์ ๋ ์๊ธฐ ๋๋ฌธ์ด๋ค. (์คํ์ปจํ ์คํธ์ outerEnvironmentReference์์ ํด๋น ์คํ์ปจํ ์คํธ์ ๋ ์์ปฌํ๊ฒฝ์ ํ๊ฒฝ๋ ์ฝ๋๋ฅผ ์ฐธ์กฐํ๊ฒ๋๋ค.)
function furtherOuter() {
const furtherOuterVariable = "further outer!";
function outer() {
function inner() {
console.log(furtherOuterVariable);
}
inner(); // further outer!
}
}
์๋ฐ์คํฌ๋ฆฝํธ ์์ง์ ์ค์ฝํ ์์ ์ฐธ์กฐํ๋ ์๋ณ์ ์ ๋ณด๊ฐ ์๋ค๋ฉด ํจ์ ํ๊ฐ ์ ์ ์์งํ๋ ๋ฐ๋ก ๋ฐ๊นฅ ์ค์ฝํ๋ก ๊ฐ์ ์๋ณ์๋ฅผ ์ฐพ๋๋ค. ๋ฐ๋ก ๋ฐ๊นฅ ์ค์ฝํ์๋ ์ฐพ๋ ์๋ณ์๊ฐ ์๋ค๋ฉด, ๊ทธ ๋ค์ ์ค์ฝํ๋ก ๊ฐ์ ์ฐพ๊ณ , ๋ง์ง๋ง์ ์ ์ญ ์ค์ฝํ๊น์ง ๊ฐ์ ์ฐพ๋๋ฐ ์ด๋๋ ์กด์ฌํ์ง ์๋๋ค๋ฉด ์ฐธ์กฐ์๋ฌ ๋ฅผ ๋ฐ์์ํจ๋ค. ์ด๋ ๊ฒ ์ค์ฝํ๊ฐ ์ฒด์ธ์ฒ๋ผ ์ฐ๊ฒฐ ๋์ด์๋ ๊ฒ์ ์ค์ฝํ์ฒด์ธ์ด๋ผ๊ณ ํ๋ค.
โ ๏ธ ์ด๋ ์ฃผ์ํด์ผํ ๊ฒ์ ๋ฐ๋ก ๋ฐ๊นฅ ์ค์ฝํ๋ ํจ์๋ฅผ ์คํํ๋ ์์ ์ ๋ฐ๊นฅ์์ญ์ด ์๋ ์ ์ธ๋๋ ์์ ์ ๋ฐ๊นฅ ์ค์ฝํ๋ฅผ ๊ฐ๋ฆฌํจ๋ค. (๋ ์์ปฌ ์ค์ฝํ)
function scopeA() {
const a = "ํธ๋์ด"; // ์ ์ธ ์์ ์ ์์ ์ค์ฝํ
function scopeB() {
console.log(a);
}
return scopeB;
}
const scopeC = scopeA();
const a = "๊ธฐ๋ฆฐ"; // ์คํ ์์ ์ ์์ ์ค์ฝํ(?)
scopeC(); // 'ํธ๋์ด'
Closure ๐ซ
const outer = () => {
const out = "outer!"; // 1. ๋ฐ๊นฅ ํจ์ outer์ ์ค์ฝํ์ ๋ณ์์ ์ธ
const inner = () => {
console.log(out); // 2. ๋ด๋ถ ํจ์ inner์ ์ค์ฝํ์์ ์ค์ฝํ์ฒด์ธ์ ํ๊ณ ๋ฐ๊นฅ ํจ์ ์ค์ฝํ์ ๋ณ์ ์ฐธ์กฐ
};
return inner; // 3. 1๊ธ ์๋ฏผ์ธ ํจ์ inner๋ฅผ ๋ฐ๊นฅ์ผ๋ก ๋ฐํ
};
const foo = outer(); // 4. foo์ innerํจ์์ ์ฃผ์๊ฐ์ด ์ ์ฅ๋จ
foo(); // 5. outerํจ์ ํธ์ถ์ ์ข
๋ฃ๊ฐ ๋์ด์ ์ค์ฝํ๊ฐ ์ฌ๋ผ์ ธ์ผ ํ์ง๋ง out์ ์ฌ์ ํ ์ ์ฐธ์กฐ๋๋ค.
ํด๋ก์ ธ๋ ์ด๋ค ํจ์(outer) ๋ด๋ถ์ ์ ์ธ๋ ํจ์(inner)๊ฐ ๋ฐ๊นฅ ํจ์(outer)์ ์ง์ญ๋ณ์(outerVariable)๋ฅผ ์ฐธ์กฐํ๋ ๊ฒ์ด ํจ์(outer)๊ฐ ์ข ๋ฃ๋ ์ดํ์๋ ๊ณ์ ์ ์ง๋๋ ํ์์ ๋งํ๋ค.
๐ก outer ํจ์ ๋ฐ๊นฅ์ผ๋ก ๋ฐํ๋ innerํจ์๊ฐ outer ํจ์์ outerVariable ๋ณ์๋ฅผ ์ฐธ์กฐํ๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ์ outer์ ์ค์ฝํ๊ฐ ์ฌ์ ํ ๋จ์์๋ค.
Closure ์์ฉ
- ํจ์๋ฅผ ์ฌ๋ฌ๋ฒ ํธ์ถํ๋ฉด ์ํ๊ฐ ์ฐ์์ ์ผ๋ก ์ ์ง๋ ๋
function outer() {
let value = 0;
return {
increase() {
++value;
console.log(value);
},
decrease() {
--value;
console.log(value);
},
};
}
const count = outer();
count.increase(); // 1
count.increase(); // 2
count.increase(); // 3
count.decrease(); // 2
count.decrease(); // 1
count.decrease(); // 0
console.log(value); // Error
์์ ๊ฐ์ด ํจ์๋ฅผ ํธ์ถํ๋ฉด ์ด์ ํจ์ ํธ์ถ ์ํ๊ฐ ๊ธฐ์ต๋๊ธธ ๋ฐ๋ ๋ ์ฌ์ฉํ ์ ์๋ค.
์ค์ ์ฌ๋ก๋ก ํ๋ก ํธ์๋ ํ๋ ์์ํฌ์ธ React ์ hook API๊ฐ ํด๋ก์ ธ๋ฅผ ํตํด์ ๊ตฌํ๋์๋ค.
hook์ ํจ์๋ฅผ ์ฌ๋ฌ ๋ฒ ํธ์ถํ๋ ์ํฉ์์ ๋ฐ์ดํฐ๋ฅผ ์ฐ์์ ์ผ๋ก ์ ์งํ๋ ๊ธฐ๋ฅ์ด๋ค.
const Counter = () => {
const [value, setValue] = useState(0); // ์ด hookํจ์๊ฐ ํด๋ก์ ธ๋ฅผ ํตํด ๊ตฌํ๋์๋ค.
return (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>+</button>
<button onClick={() => setValue(value - 1)}>-</button>
</div>
);
};
์ํvalue๊ฐ ๋ฐ๋์ด ๋ ๋๋ง์ด ๊ณ์ ์ผ์ด๋จ์ ๋ฐ๋ผ Counter ํจ์๊ฐ ์ฌ๋ฌ ๋ฒ ํธ์ถ๋๋ค. ํ์ง๋ง useState๋ 0์ด ์๋๋ผ ์ด์ ์ํ value์ ๊ฐ์ ์ ์งํ๊ณ ์๋ค. ์ด๋ useState ์ ์ธ ์์ ์ ๋ฐ๊นฅ ๋ณ์์ 0์ ์ด๊ธฐํํ ๋ค์, setValue๋ก ํด๋น ๋ฐ๊นฅ ๋ณ์๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒ์ ๋๋ค. ๋ค์ Counter๊ฐ ํธ์ถ๋๊ณ ๊ทธ ์์ useState๊ฐ ๋ค์ ํธ์ถ๋๋ฉด ๋ณ๊ฒฝ๋ ๋ฐ๊นฅ ๋ณ์๋ฅผ value๋ก ๋ฐํํฉ๋๋ค.
- ๋ณ์๋ฅผ ์จ๊ฒจ์ผ ํ ๋
let value = 0;
function increase() {
console.log(++value);
}
function decrease() {
console.log(--value);
}
function unknown() {
value = -100000;
}
increase(); // 1
unknown(); // value: -100000
decrease(); // -100001
์ ์ญ ๋ณ์๋ก ์ ์ธํด์ ์ฌ์ค ์์ ๊ฐ์ ํด๋ก์ ธ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๊ธด ์์ผ๋ ๋ง์ฝ ์ฝ๋์ ์์ด ๋ง์์ง๊ณ ๊ธธ์ด์ง๊ฒ ๋๋ค๋ฉด ์ด๋์ ํด๋น ์ ์ญ๋ณ์์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋์ง ์ถ์ ์ด ์ด๋ ค์์ง๊ฒ ๋๊ณ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ ์ด๋ ค์์ ๊ฒช๊ฒ ํ๋ค.
const counterCreator = () => {
let value = 0;
return {
increase() {
console.log(++value);
},
decrease() {
console.log(--value);
},
};
};
const counter = counterCreator();
function unknown() {
value = -100000;
}
counter.increase(); // 1
unknown(); // error: Uncaught ReferenceError: value is not defined
counter.decrease(); // ์ ์์ ์ผ๋ก ์งํ๋๋ค๋ฉด 0
์์ ๊ฐ์ด ํด๋ก์ ๋ก ํจ์๋ฅผ ๋ง๋ค๋ฉด ์ธ๋ถ์์ ๋ณ์์ ์ ๊ทผ์ ๋ ํผ๋ฐ์ค ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
- ํจ์๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํด์ผํ ๋
์นด์ดํฐ๋ฅผ ๋ ๊ฐ์ง ๋ ๋ฆฝ์ ์ผ๋ก ํด์ผํ๋ ์ํฉ์ด ์๋ค๊ณ ๊ฐ์ ํ์
let myValue = 0;
let yourValue = 0;
function increaseMyCounter() {
console.log(++myValue);
}
function decreaseMyCounter() {
console.log(--myValue);
}
function increaseYourCounter() {
console.log(++yourValue);
}
function decreaseYourCounter() {
console.log(--yourValue);
}
์ด๋ ๊ฒ ํด๋ฒ๋ฆฌ๋ฉด ์ฝ๋๊ฐ ๋๋ฌด ๊ธธ์ด์ง๊ณ ์ง์ ๋ถํด ๋ณด์ธ๋ค.
๊ทธ๋ฌ๋ฉด ํด๋ก์ ธ๋ก ๋ง๋ค์ด๋ณด์
const counterCreator = () => {
let value = 0;
return {
increase() {
console.log(++value);
},
decrease() {
console.log(--value);
},
};
};
const myCounter = counterCreator();
const yourCounter = counterCreator();
myCounter.increase(); // 1
myCounter.increase(); // 2
yourCounter.increase(); // 1
myCounter.decrease(); // 1
์์ ๊ฐ์ด myCounter์ yourCounter์ ์ํ๋ ๋ ๋ฆฝ์ ์ด๊ฒ ๋์๋ค.
๐ ํด๋ก์ ๋ฅผ ํ์ฉํด ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ ์ฑ์ด ํจ์ฌ ์ข์์ง ๊ฒ์ ๋ณผ ์ ์๋ค.