[바닐라코딩 프렙] Closure 예제 모음

Suh, Hyunwook·2021년 9월 5일
0
post-thumbnail

클로저(Closure)는 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상이다.

좀 더 자세히 설명하자면, 어떤 함수 A(=외부 함수)에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우, A 함수의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상을 말한다.

오늘은 클로저의 속성을 이해할 수 있도록, Dmitri Pavlutin님의 Closure 퀴즈를 소개하려고 한다.

Q1. 아래 코드에서 출력되는 값은 무엇인가?

(function immediateA(a) {
  return (function immediateB(b) {
    console.log(a); // What is logged?
  })(1);
})(0);

해설: immediateA 함수 parameter로 0을 넘기며 실행되면, immediateB 함수에 parameter로 1을 넘기며 실행한 값을 return하게 된다. A 함수가 종료되었으나, a는 가비지컬렉팅에서 제외되며, immediateB 함수에서는 외부변수인 a변수인 0을 출력하게 된다.

Q2. 아래 코드에서 출력되는 값은 무엇인가?

let count = 0;
(function immediate() {
  if (count === 0) {
    let count = 1;
    console.log(count); // What is logged?
  }
  console.log(count); // What is logged?
})();

해설: 10이 출력된다. immediate의 외부 scope는 count이며, global scope에 선언된 count 변수를 closure로 저장한다. 그런데, if문(블록 스코프)안에서 count가 새로 선언되어 첫 번째에서는 1이 출력된다. 그러나, 두 번째에서는 외부 scope에 있는 count를 참조하므로, 0이 출력된다.

Q3. 아래 코드에서 출력되는 값은 무엇인가?

for (var i = 0; i < 3; i++) {
  setTimeout(function log() {
    console.log(i); // What is logged?
  }, 1000);
}

해설: 4가 네 번 출력되게 된다. i 값이 증가하며, setTimeout을 3회 실행하게 되면, log함수는 Web API를 거쳐 event queue에서 차례로 3회 실행되게 되는데, var는 전역 컨텍스트를 참조하므로, i의 값은 4이다. let을 사용하면 블록컨텍스트마다 i 값을 저장해놓을 수 있으나, var을 유지할 경우, 클로저를 활용하여 이를 해결할 수 있다.

for (var i = 0; i < 3; i++) {
  setTimeout(function(i) {
	return function log() {
    console.log(i); // What is logged?
  }}(i), 1000);
}

이 경우, 각 setTimeout의 function에서 넘겨받은 인수 i에 대해 내부함수에서 클로저가 발생하여, event queue에서 내부 함수가 실행될 때, 실행 시의 컨텍스트를 참조하여 i 값을 출력하므로, 0,1,2가 출력되게 된다.

Q4. 아래 코드에서 출력되는 값은 무엇인가?

function createIncrement() {
  let count = 0;
  function increment() { 
    count++;
  }
  let message = `Count is ${count}`;
  function log() {
    console.log(message);
  }
    return [increment, log];
}
const [increment, log] = createIncrement();
increment(); 
increment(); 
increment(); 
log(); // What is logged?

해설: 'Count is 0'가 출력되게 된다. increment()가 3회 출력되어, count의 값은 3이 되었으나, message 변수는 'Count is 0'를 가지고 있으며, log함수는 message 변수를 참조하므로, 위와 같이 출력된다.

function log() {
  let message = `Count is ${count}`;
  function log() {
    console.log(message);
  }
}

message를 안에 선언하여, message의 count가 외부 scope를 참조하도록 해야, count의 상승분을 반영할 수 있다.

Q5. 아래 코드를 수정하여, stack.item이 접근불가능하게 만드시오.

function createStack() {
  return {
    items: [],
    push(item) {
      this.items.push(item);
    },
    pop() {
      return this.items.pop();
    }
  };
}
const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5
stack.items; // => [10]
stack.items = [10, 100, 1000]; // Encapsulation broken!

해설: itemscreateStack()의 scope에 넣어서, 클로저로 변수를 접근하게 하며, 외부에서 프로퍼티로 접근하지 못하게 한다. this도 붙일 필요가 없다. items 변수가 클로저로 저장되기 때문이다.

function createStack() {
  // Write your code here...
  const items = [];
  return {
    push(item) {
      items.push(item);
    },
    pop() {
      return items.pop();
    }
  }
}
const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5
stack.items; // => undefined

Q6. 아래 코드를 작성하여, multifly()함수를 완성하시오.

function multiply(num1, num2) {
  // Write your code here...
  if (num2 !== undefined) {
   	return num1 * num2; 
  } 
  return function doMultifly(num2) {
    return num1 * num2;
  }
}
multiply(4, 5); // => 20
multiply(3, 3); // => 9
const double = multiply(2);
double(5);  // => 10
double(11); // => 22

해설: multiply 함수의 경우, 두 인자를 받지만, num2가 생략되었을 경우, return 된 doMultiply함수에서 num1은 클로저 변수로 저장된다. 이 때문에, num2만 인자로 받아 연산이 가능하다.

0개의 댓글