[JavaScript] 클로저
2022.11.08
💡 오늘의 JavaScript 공부
▶️ 클로저
클로저란
[ 함수 ]와 [ 그 함수가 선언된 어휘적( Lexical ) 환경의 조합 ]을 말한다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.
클로저 함수의 특징
- 함수를 리턴하는 함수
const adder = x => x + y
adder(5)(7); // 12
typeof adder(5) // 'function'
adder(5) // y => x(5) + y
// 위 코드와 동일한 코드는
const adder = function(x) // x = 외부함수
return function(y){ // y = 내부함수
return x + y;
}
}
// 리턴값이 function(y) 함수의 형태
// 위에 보이는 것 처럼 스코프가 분리되어 있어
// function(x)는 function(y) 접근 불가능
// function(y)는 function(x) 접근 가능
여기서 가장 중요한 것은
외부함수는 내부함수에 접근 X , 내부함수는 외부함수에 접근 O
- 데이터를 보존하는 함수
외부함수의 실행이 끝나더라도, 외부함수 내 변수를 사용할 수 있다.
일반적인 함수는, 함수 실행이 끝나고 나면 함수 내부의 변수를 사용할 수 없다. 이와 다르게, 클로저는 외부함수의 실행이 끝나더라도, 외부 함수 내 변수가 메모리 상에 저장된다.
// HTML 문자열 생성기
const tagMaker = tag => content => `<${tag}> ${content} </${tag}>`
const divMaker = tagMaker('div')
divMaker('hello') = '<div> hello </div>'
- 정보의 접근 제한 ( 캡슐화 )
// 클로저 모듈 패턴
const makeCounter = () => {
let value = 0;
return {
increase: () => {
value = value + 1
},
decrease: () => {
value = value - 1
},
getValue: () => value
}
}
const counter1 = makeCounter()
이처럼 클로저를 이용해 내부함수를 단 하나만 리턴하는 것에 그치지 않고, 객체에 담아 여러 개의 내부 함수를 리턴한도록 한다.
makeCounter 함수를 바꾸지 않고, value 라는 변수에 값을 새롭게 할당할 수 있는 방법이 있을까?
대답은 No이다. 외부 스코프에서는 내부스코프에 접근할 수 없다는 규칙에 의해, 어떤 경우에도 ‘value’는 직접 수정이 불가능하다.
그러나 리턴하는 객체가 제공하는 메서드를 통해 ‘value’ 값을 간접적으로는 수정할 수 있다.
만일 스코프로 value의 값을 감싸지 않았다면, value의 값은 전역변수였을 것이다. 하지만 makeCounter 함수가 value의 값을 보존하고 있기 때문에 따로 전역변수를 만들 필요가 없는 것이다.
상태를 은닉
const x = 1;
function outer(){
// 지역변수 x 선언
const x = 10;
// inner함수 정의
const inner = function(){console.log(x)}
// inner함수를 그대로 리턴
return inner
}
// outer의 역할은 여기서 끝
// inner함수가 정의된 outer는 사라지지만 inner함수는 상위 스코프인 outer의 변수를 기억하고있다.
const innerFunc = outer();
innerFunc() // outer함수 내부에 있는 inner함수를 실행한다.
중첩 여부와 상관없이 처음 정의된 곳의 상위 스코프가 정적으로 결정되었기 때문
▶️ JavaScript의 함수는 일급 객체
자바스크립트의 함수는 변수에 할당도 가능하고, 리턴도 할 수 있고, 매개변수로도 넣을 수 있고 등등…
근데 함수가 함수를 리턴하는 형태가 나온다? -> 일단 클로저라고 의심!
- 중첩함수
- 중첩함수(리턴되고있는 함수)가 외부함수의 변수를 참조한다? -> 이게 바로 클로저 \
왜? 변수를 안전하게 보호하기 위해서(은닉) -> 리턴된 함수를 사용하지 않으면 다른 요인으로 변수 못바꾼다.
const increase = function(){
let num = 0;
return function(){ // === increase1
return ++num;
}
}
const increase1 = increase();
console.log(increase1()); // 1
console.log(increase1()); // 2
console.log(increase1()); // 3
let num = 0;
const increase = function(){
return ++num;
}
console.log(increase()) // 1
console.log(increase()) // 2
console.log(increase()) // 3
num = 5
num++
console.log()
// 외부에 의해 영향을 받아서 num변수를 지킬 수 없음
📌 왜 원시 자료형이라고 부를까?
원시 자료형은 모두 ‘하나’의 데이터를 담고있다. 옛날 컴퓨터에서는 데이터 보관함 한칸에 하나의 데이터만 넣을 수 있었기에 비슷한 느낌으로 원시 자료형이라 부른다.
원시자료형의 보관함인 변수에는 하나의 원시자료형만 담을 수 있다. 이 특징은 참조 자료형의 heap과는 구분되는 모습이다.
const num1 = 123;
const num2 = 12345;
// 이렇게 변수에는 데이터 크기와 상관없이
// 하나의 데이터만 담을 수 있다.
📌 참조 자료형?? Heap?
참조 자료형에서는 하나의 데이터가 아닌 여러 데이터가 담긴다.
원시 자료형이 하나의 데이터 보관함에 저장되었다면 참조자료형은 별도의 공간 heap을 생성해 그 곳의 주소에 데이터를 저장한다고 생각하면 된다. 이 특별한 공간은 데이터에 맞게 사이즈를 동적으로 변하기도 한다.
나름 예시를 들어보자면
let a = [4,5,6]; // 1번 사물함:a 주소:1
let b = ['hi',1,2]; // 2번 사물함:b 주소:2
let c = [true,false] // 3번 사물함:b 주소:3
a[0] =3;
b.pop();
c.push('a')
--------------------------------------
// heap 공간
// 주소 1 = 3,4,5
// 주소 2 = 'a',1
// 주소 3 = true,false,'a'
이런 식으로 이해하면 될 것 같다.
📌 Quiz
// 다음 코드들의 x의 값은?
let x = 2;
let y = x;
y = 3;
// x = 2 이다
// 원시자료형이기에 y에 다른 값을 할당해도
// x는 변하지 않는다.
let x = {foo:3};
let y = x;
y.foo = 2
// 참조자료형이기에 기본값에 영향을 준다.
// x = 2 이다
let x = {foo:3};
let y = x;
y = 2
// 참조자료형으로 주소값을 할당받았지만,
// y를 원시타입으로 할당했으니 기본값에 영향을 받지 않는다.
// x = 3 이다
Comments