본문 바로가기

코드스테이츠(Pre)

자바스크립트_클로저

클로저

: 외부함수가 종료된 뒤에도 내부함수는 외부함수의 execution context가 사용했던 메모리 공간에 접근 가능

 

클로저의 작동순서

 

function greet(whattosay){
	return function(name){
    	console.log(whattosay + ' ' +name);
    }
}

greet('hi')('tony') // hi tony

// greet() 실행이 끝나면 greet() execution context 사라졌지만
var sayHi = greet('hi');

// sayHi()에서 whattosay를 참조!
sayHi('tony'); // hi tony

greet('Hi') 실행 > execution context 생성 + whattosay 변수 생성
greet()코드 실행 끝 -> execution context 삭제, whattosay 메모리상 존재
                                             sayHi('Tony')실행 > execution context 생성 + name변수 생성                                                                                                          console.log(whattosay 변수 탐색) > 발견 X > 스코프체인을 따라 outer environement로 

 

메모리상 존재하는 whattosay 변수에 접근

 

클로저 예제

function buildFunction(){
	var arr = [];
    for(var i = 0; i<3 i++){
    	arr.push(
        	function(){
            	console.log(i);
                }
           )}
     return arr;
}

var fs = buildFunction();
fs[0]();	// 3
fs[1]();	// 3
fs[2]();	// 3

buildFunction() 이 먼저 실행되면 노란색으로 표시한 for문 안의 로직은

expression 아니라 statement , 실행되진 않은 함수선언 자체가 배열에 각각 push

(함수라는 객체를 만들뿐 실행되지 않음)

 

buildFunction() 실행이 끝나면 exectuion context는 삭제되지만 해당 변수들은 메모리에 남아있다.

이후 fs[0,1,2] 함수가 각각 실행되어 내부로직을 실행할 때 클로저 특성에 따라 arr과 i를 참조하게 된다.

fs() 를 호출하는 시점에서 i의 값은 3이기 때문에 3번의 실행 모두 i=3 을 참조 

해서 0,1,2 가 아닌 3,3,3을 출력하게 된다.

 

0, 1, 2 를 출력하려면?

1. let 키워드

function buildFunction2(){
	var arr = [];
    for(var i = 0; i<3; i++){
		let j = 1;
        arr.push(
        	function(){
            	console.log(j);
                }
           	})
    return arr;
}

var fs = buildFunction2();
fs[0]();	// 0
fs[1]();	// 1
fs[2]();	// 2

 

2. IIFE활용

function buildFunction3(){
	var arr = [];
    for(var i = 0; i<3 i++){
    	arr.push(
        	(function(j) {
            	return function(){
                	console.log(j);
                    }
                })(i);
         )}
     return arr;
}

var fs = buildFunction();
fs[0]();	// 0
fs[1]();	// 1
fs[2]();	// 2

각각의 execution context 를 생성하고 그때마다 i를 따로 저장하면 가능한데 ,

execution context를 생성하는 유일한 방법은 함수를 실행하는 것 -> IIFE 활용

 

 

클로저 사용을 확인 할 수 있는 예시

function foo(){
    var color = 'blue';
    function bar() {
    	console.log(color);
    }
    bar();
}
foo();

bar 함수는 foo안에서 color를 참조 

bar는 foo 안에서 정의되고 실행되었을 뿐, foo 밖으로 나가지 않았기 때문에 클로저라 하지 않는다.

 

<--- 클로저 사용 --->
var color = 'red';
function foo() {
    var color = 'blue';	// 2
    function bar(){
    	console.log(color);	// 1
    }
    return bar;
}
var baz = foo();	// 3
baz();	// 4

1. bar는 color를 찾아 출력하는 함수로 정의

2. bar는 outer environment 참조로 foo의 environment 저장

3. bar를 global의 baz란 이름으로 데려옴

4. global에서 baz(=bar) 호출

5. bar는 자신의 스코프 안에서 color 찾음

6. 없음으로 outer environment를 찾아감

7. foo 스코프 탐색 color를 찾아 blue 반환

8. blue 리턴

bar는 자신이 생성된 렉시컬 스코프에서 벗어나 global에서 baz라는 이름으로 호출, 스코프 탐색은 현재 실행 스택과 관련없는 foo를 거쳐갔다.  

baz를 bar로 초기화 할때는 이미 bar의 outer lexical environment를 foo로 결정한 이후이기 때문의 bar의 생성과 직접적인 관련이 없는 global에서 아무리 호출하더라도 여전히 foo에서 color를 찾는다. 

이런 bar(또는 baz)같은 함수를 클로저라 부른다.

 

클로저 모듈패턴

function likeCounter(){
  let like = 0;
  function changeBy(val){
    like += val;
  }
  return {
  iLike: function(){
      changeBy(1);
    },
  disLike: function(){
      changeBy(-1);
    },
  checking: function(){
      return like;
    }
  }
};
const Like = likeCounter();

likeCounter 함수엔 like라는 변수와 changeBy라는 like 값을 바꿀수 있는 함수가 존재

그리고 iLike, disLike, checking이라는 메소드를 가진 객체를 반환합니다.

맨 아랫줄에서는 Like라는 상수에 리턴 값이 담기죠.

Like.iLike(); // like === 1
Like.iLike(); // like === 2
Like.iLike(); // like === 3
Like.checking(); // 3
Like.disLike(); //like === 2

메소드를 통해 like의 값 증감

중요한 것은 함수 외부에서는 like나 changeBy 함수에 직접 접근할수 없고, 객체의 메소드를 통해서만 값 변동가능

 

템플릿 활용

function elementMaker(tagName) {
  var startTag = '<' + tagName + '>';
  var endTag = '</' + tagName + '>';
  return function(content) {
    return startTag + content + endTag;
  }
}

elementMaker('div')('hello world'); // <div>hello world</div>
var divMaker = elementMaker('div');
divMaker('code states'); // <div>code states</div>
divMaker('great'); // <div>great</div>

var h1Maker = elementMaker('h1');
h1Maker('Headline'); // <h1>Headline</h1>