Jaeilit

코어 - 클로저 본문

JavaScript

코어 - 클로저

Jaeilit 2022. 6. 30. 16:24
728x90

클로저 - MDN

클로저는 함수와 그 함수가 선언 될 당시의 어휘적  환경의 상호관계에 따른 현상

 

예제1)

var outer = function () {
  var a = 1;
  var inner = function () {
		console.log(++a) // 2
    return ++a
  }
  
  inner()
}

console.log(outer()) // undefined
console.log(outer()) // undeinfed

설명은 그림과 같지만,

 

outer() 함수 호출 당시 실행컨텍스트는 환경레코드에 a 와 inner 를 담고 외부환경으로는 outer 를 담습니다.

inner() 함수는 호출 당시에 실행컨텍스트에 환경레코드로 담을 변수가 없습니다.

inner가 ++a 를 해줘야하는데 inner() 환경레코드에는 없으니 밖으로가서 외부환경을 참조합니다.

외부환경을 살펴보니 a 와 Inner가 있습니다.

여기서 a를 ++ 해주고 outer() 함수 안의 환경레코드에서도 a 를 2로 바꿔줍니다.

 

inner() 함수 안에서 console.log(++a) 해보면 2가 출력이 되지만,

하지만 outer 함수는 return 하는게 없기 때문에 외부에서 함수를 호출해도 undefined 입니다.

 

* outer() 함수 종료 시점에서는 a와 Inner 를 참조하는 변수가 하나도 없게 되므로 가바지컬렉터의 수집 대상이 됩니다.

 

예제2)

var outer = function () { 
  var a = 1;
  var inner = function () { 
        return ++a;
  };
  console.log(inner(), a) // 2 2
  console.log(inner(), a) // 3 3
  console.log(inner(), a) // 4 4
  console.log(inner(), a) // 5 5
  
  return inner(); 
};

var outer2 = outer();
console.log(outer2); // 6
console.log(outer2); // 6
console.log(outer2); // 6
console.log(outer2); // 6

사실 console.log(outer2()) 의 출력은 2 입니다.

근데 6인 이유는 outer 함수 내부에서 console.log 안에서 Inner()를 계속 호출하기 때문입니다.

 

return 을 inner() 함수가 아닌 a를 return 하면 a 는 1이기 떄문에 그냥 1을 출력 할 것이다.

 

inner 함수 내부에서는 외부환경에서 a를 참조하지만 outer() 함수 종료 시점에는 inner() 함수도 종료되어 있기 때문에 a를 참조하지않습니다.

 

예제3)

var outer = function () {
  var a = 1;
  var inner = function () {
    return ++a
  }
  return inner
}

console.log(outer()) // f inner()
console.log(outer()) // f inner()
console.log(outer()) // f inner()

let outer2 = outer()

console.log(outer2()) //2
console.log(outer2()) //3
console.log(outer2()) //4
console.log(outer2()) //5

inner 함수 실행 결과가 아닌 inner 함수 자체를 반환했습니다.

그리고 outer() 를 outer2 변수에 저장해서 함수를 실행결과 값만 받았습니다.

 

outer() 함수가 종료 될 시점에 inner 함수를 참조하고 있고 inner 함수는 a를 참조하고 있기 때문에 가비지 컬렉터 대상이 되지 않습니다.

가비지 컬렉터의 대상이 되지 않았으니 메모리를 여전히 할당하고 있을 것이고 이런현상으로 메모리 누수를 걱정 해야 할수도 있습니다.

하지만 참조 관계를 끊어내면 다시 가비지 컬렉터의 대상이 될 것이고 메모리에서 해제 될 것입니다.

 

 

// undefined 또는 null 을 할당하여 참조관게를 끊는다.
outer2 = null

클로저의 활용

1. 콜백 함수 내부에서 외부 데이터를 사용하고자 할 때

forEach 콜백함수 안에서 addEventListener는 fruit 을 외부에서 참조하고 있습니다.

addEventListener 의 콜백인 함수 B 의 쓰임새가 콜백 함수에 국한되지 않는 경우라면 반복을 줄이기 위해 외부로 분리하는 편이 나을 수 있습니다.

alertFruit 이라는 함수로 fruit을 인자로 받아서 alert 를 실행합니다.

하지만 이 함수는 alertFruit(fruits[1]) 에 대해서만 banana 등 올바른 값이 나오고

함수 호출이 되지 않는 li 태그를 클릭 할 시에는 [object MouseEvent] 라는 값이 출력됩니다.

 

bind 를 활용해서 이 문제를 풀어나갈수도 있지만 고차함수를 활용하여 풀어내는 것이 함수형 프로그래밍에서 더 자주 쓰이는 방식입니다.

alertFruitBuilder 의 인자로 fruit 을 넘겨주고 함수는 함수를 리턴함으로써 콜백함수의 형태를 취하게 됩니다.

접근권한제어(정보은닉)

class 에는 private 로써 외부접근을 막을 수 있고 외부에 노출하지 않을 수 있습니다.

class 가 아닌 변수 자체에는 접근권한을 막을 수 있는 방법이 없습니다.

 

하지만 클로저를 활용하면 접근 권한을 제어 할 수 있습니다.

function Counter() {
  // 카운트를 유지하기 위한 자유 변수
  var counters = 0;

  // 클로저
  this.increase = function () {
    return ++counters;
  };

  // 클로저
  this.decrease = function () {
    return --counters;
  };
}

const counter = new Counter();

console.log(counter.increase()); // 1
console.log(counter.decrease()); // 0
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.increase()); // 3
console.log(counter.increase()); // 4
console.log(counter.increase()); // 5
console.log(counter.decrease()); // 4

console.log(counter.counters) // undefined

console.log(counter)

//Counter {
// 		increase: ƒ (),
// 		decrease: ƒ (),
// 		__proto__: { constructor: ƒ Counter() }
// }

은닉화가 된 카운터 변수 counters는 함수 외부에서 참조 할 수 없고 오직 Counter 함수 내부 로직에 의해서만 값이 변하게 됩니다.

 

클로저는,

 

함수의 종료시점에 내부 변수를 아무도 참조하지 않아 가비지 컬렉팅 당하는 현상을 내부 함수를 Return 함으로써 함수의 종료 시점에 내부 함수를 참조하게 만들고 내부 함수는 내부의 변수를 참조하게 만드는 관계를 만들어서 가비지컬렉팅을 당하지 않게 합니다.

 

만들어진 클로저의 내부 변수는 오직 내부의 함수에서만 접근이 가능하며,

외부에서 접근하지 못하게 private 한 상태가 되어 값을 조작하지 못하게 됩니다. (은닉화)

 

728x90

'JavaScript' 카테고리의 다른 글

이벤트버블링  (0) 2022.10.01
코어 - 프로토타입  (0) 2022.07.12
코어 - 콜백함수  (0) 2022.06.20
코어 this(2)  (0) 2022.06.08
코어 this  (0) 2022.06.07