클로저

느리게 따라가기·2023년 3월 15일
0

javascript

목록 보기
6/6

1. 정의(MDN)

MDN : A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)

  • 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
  • 내부함수에서 외부함수로 접근할 수 있게 해준다.
  • 일반적 용례로는 외부함수보다 중첩(내부)함수가 더 오래 유지되어,
  • 중첩 함수가 생명주기를 종료한 외부 함수의 변수를 참조할 경우(=> 기술적으로 외부 함수의 렉시컬 환경을 사용하는 경우),
  • 이러한 중첩함수를 클로저라 부른다.
  • 자바스크립트에서 함수가 생성시점에 항상 생성된다.
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

2. 렉시컬 환경(Lexical Environment)

특정 코드가 작성, 선언된 환경(장소)을 의미한다.

  • 스크립트 전체
  • 코드블럭 {...}
  • 함수

2.1 렉시컬의 구성

  • EnvironmentRecord : 현재 실행중인 코드 환경의 this값과 선언된 모든 변수와 함수가 저장되는 곳
  • OuterLexicalEnvironmentReference : 외부 렉시컬 환경의 참조값

2.2 실행 컨텍스트와 렉시컬의 관계

2.3 렉시컬과 스코프체인

  • OuterLexical Environment Refernce를 통해 스코프 체이닝이 이뤄진다.

함수레벨 스코프

  • var를 이용한 변수 선언
  • local(지역)은 함수내부를 말하고, 이는 코드 블럭이 아닌 함수에 의해서만 지역 스코프가 생성된다는 의미이다.

블럭 레벨 스코프

  • let, const를 이용한 변수선언
  • {} 내부안의 스코프(if,for,while,switch 등)

참고사항 => var는 배제하고, let이나 const를 이용해서 선언하자!!

렉시컬 스코프

  • 동적 스코프(Dynamic Scope) : 함수 정의 위치에 따른 상위 스코프 결정방식
  • 정적스코프(Static Scope==Lexical Scope) : 함수의 정의 위치에 따른 상위 스코프 결정 방식
  • 자바스크립트는 정적스코프(Lexical Scope) 방식을 따른다.
  • 자바스크립트는 이를 구현하기 위해 함수객체의 내부슬롯[[Environment]]에 상위스코프의 참조를 저장한다.

2.3.1 렉시컬 스코프에 대한 예제

const x=1;
function foo(){
  const x=10;
  bar();  
}
function bar(){
  console.log(x);
}
foo();
bar();

코드 평가 순서

  1. 함수 실행 컨텍스트 생성
  2. 함수 렉시컬 환경 생성

    2.1 함수 환경 레코드 생성
    2.2 this 바인딩
    2.3 외부 렉시컬 환겨에 대한 참조 결정

내부슬롯[[Environment]]에 상위스코프가 저장된다.

3. 클로저와 렉시컬 환경

const x=1;										//line 1
function outer(){								//line 2
  const x=10;									//line 3
  const inner = function(){						//line 4
     console.log(x);							//line 5
  };	// inner와 outer사이의 clousre 성립		//line 6
  return inner;									//line 7
}  		// outer와 전역컨텍스트와의 closure 성립	//line 8
const innerFunc=outer();						//line 9
innerFunc();  // 10								//line 10

3.1 전역평가 후

전역평가후

3.2 outer() 실행중(break line 9)

outer 실행중

3.3 inner 실행중(break line 10)

inner 실행중

※ 중요사항

  • inner함수에서 outer의 변수x를 사용
  • 이경우 outer 실행 후(line 9) outer Execution Context는 상실하지만, outer함수의 렉시컬 환경은 유지된다.
  • 이러한 상황의 inner를 통상적으로 클로저라 한다.
  • 자바스크립트는 기본적으로 모든 함수는 클로저로 작동하지만, 통상적으로는 실행컨텍스트에서는 사라지고 렉시컬 환경은 유지되는 상황을 일컫는다.

4. 클로저의 활용

  • 상태유지
  • 전역변수 사용억제
  • 고차함수의 활용
  • 정보은닉

4.1 상태유지

현재 상태를 기억하고 변경된 최신 상태를 유지하는데 유용하다.

<html>
<body>
<button class="toggle">toggle</button>
<div class="box" style="width:100px; height:100px;background:red;"></div>
<script>
	let box = document.querySelector('.box');
	let toggleBtn =  document.querySelector('.toggle');
	
	let toggle=(function(){
		let isShow=false;
		//1. 클로저 반환
		return function(){
			box.style.display =  isShow ? 'block':'none';
			isShow=!isShow;
		};
	})();
	toggleBtn.onclick=toggle;

</script>
</html>
  • isShow를 기억하고 변경한다.
  • 클로저가 없으면 전역변수로 정의하고 사용해야 한다.
  • 전역의 경우 누구나 접근, 변경할수 있으므로 많은 사이드이펙트를 유발한다.
  • 이러한 경우 클로저가 유용하다.

4.2 전역변수 사용억제

<html>
<body>
<button id="increase">+</button>
<p id="count">0</p>
	
<script>
	let increaseBtn = document.querySelector('#increase');
	let count =  document.querySelector('#count');

	let increase=(function(){
		// 상태유지를 위한 변수
		let counter=0;

		// 클로저 리턴
		return function(){
			return ++counter;
		};		
	})();

	increaseBtn.onclick=function(){
		count.innerHTML=increase();
	}
</script>
</body>
</html>
  • 4.1과 유사한 상태
  • 상태유지를 위해 전역이 아닌 클로저를 활용하여 임의로 수정불가
  • increase를 이용해서만 수정가능

4.3 고차함수의 활용

  • 고차 함수는 함수를 인자로 받거나 또는 함수를 반환함으로써 작동 하는 함수
  • 일급객체로서의 함수
// predicate 함수를 argument로 받아 클로저 내부에서 실행
function makeCounter(predicate) {
  // 카운트 상태를 유지하기 위한 변수
  var counter = 0;
  // 클로저를 반환
  return function () {
    counter = predicate(counter); // argument로 받은 predicate 함수실행
    return counter;
  };
}

// 보조 함수
function increase(n) {
  return ++n;
}

// 보조 함수
function decrease(n) {
  return --n;
}

// 함수로 함수를 생성한다.
// increaser의 렉시컬 환경이 생성된다.
const increaser = makeCounter(increase);
console.log(increaser()); // 1
console.log(increaser()); // 2

// increaser와 별개의 decrease 렉시컬 환경이 생성된다.
// => increase와 decrease의 렉시컬 환경이 다르므로 counter값이 서로 독립적으로 서로 연동되지 않는다.
const decreaser = makeCounter(decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2
increase, decrease 렉시컬 환경
  • counter가 각각의 렉시컬로 분리되어 있다
  • 원인은 makeCounter 함수를 두번 호출해서 발생하는 문제 => 렉시컬 분리
  • 원하는 방식은 증감함수가 하나의 변수를 변경하는 것이므로 아래와 같이 변경
<script>
  // 즉시 실행함수 => 한번만 호출(maekCounter 대체)
  const counter = (function(){
    let count=0;
    return function(predicate){
      count=predicate(count);
      return count;
    }
  }()); 
  // 보조함수
  function increase(n){
    return ++n;
  }
  function decrease(n){
    return --n;
  }
  // 호출 

  console.log(counter(increase)); // 1
  console.log(counter(increase)); // 2

  // 자유변수를 공유
  console.log(counter(decrease)); // 1
  console.log(counter(decrease)); // 0
</script>
count(increase), count(decrease) 렉시컬 환경

4.4 정보은닉

profile
두걸음 뒤에서.. 그래도 끝까지!!

0개의 댓글