3 minute read

2022.11.08

💡 오늘의 JavaScript 공부

▶️ 클로저

클로저란

[ 함수 ]와 [ 그 함수가 선언된 어휘적( Lexical ) 환경의 조합 ]을 말한다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.

클로저 함수의 특징

  1. 함수를 리턴하는 함수
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

  1. 데이터를 보존하는 함수

외부함수의 실행이 끝나더라도, 외부함수 내 변수를 사용할 수 있다.

일반적인 함수는, 함수 실행이 끝나고 나면 함수 내부의 변수를 사용할 수 없다. 이와 다르게, 클로저는 외부함수의 실행이 끝나더라도, 외부 함수 내 변수가 메모리 상에 저장된다.

// HTML 문자열 생성기 
const tagMaker = tag => content => `<${tag}> ${content} </${tag}>`

const divMaker = tagMaker('div')
divMaker('hello') = '<div> hello </div>'
  1. 정보의 접근 제한 ( 캡슐화 )
// 클로저 모듈 패턴 
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의 함수는 일급 객체

자바스크립트의 함수는 변수에 할당도 가능하고, 리턴도 할 수 있고, 매개변수로도 넣을 수 있고 등등…

근데 함수가 함수를 리턴하는 형태가 나온다? -> 일단 클로저라고 의심!

  1. 중첩함수
  2. 중첩함수(리턴되고있는 함수)가 외부함수의 변수를 참조한다? -> 이게 바로 클로저 \

왜? 변수를 안전하게 보호하기 위해서(은닉) -> 리턴된 함수를 사용하지 않으면 다른 요인으로 변수 못바꾼다.

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