Hi

클로저 본문

WEB(웹)/javascript

클로저

SharingWorld 2018. 7. 7. 13:25

클로저

클로저의 개념

외부 함수의 컨텍스트가 반환되더라도 변수객체는 반환되는 내부 함수의 스코프 체인에 그대로 남아있어야만 접근할 수 있다. 이것이 바로 
-> 클로저 : 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수


ex) outerFunc에서 선언된 x와 같은 변수를 자유 변수(Free variable)라고 한다. closure라는 이름은 함수가 자유 변수에 대해 닫혀있다(closed, bound)는 의미. -> 자유 변수에 엮여있는 함수 (우리말로 표현하자면) 
- 자바스크립트로 클로저를 구현하는 전형적인 패턴 -

function outerFunc() {
	var x = 1;
	return function() { <- 클로저
		/* x와 arguments를 활용한 로직 */
	} // 
}

var new_func = outerFunc();

/* outerFunc 실행 컨텍스트가 끝났다 */

new_func();


[NOTE] 클로저는 여러 언어에서 차용되고 있는 특성이다. 특히 함수를 일급 객체로 취급하는 언어(함수형 언어 functional language)에서 주요하게 사용되는 특성이다.


클로저의 활용

특정 함수에 사용자가 정의한 객체의 메서드 연결하기

function HelloFunc(func) {
	this.greeting = "hello";
}

HelloFunc.prototype.call = function(func) {
	func ? func(this.greeting) : this.func(this.greeting);
} // this.func와 this.greeting의 this는 생성된 인스턴스를 가리킨다.

var userFunc = function(greeting) {
	console.log(greeting);
}

var objHello = new HelloFunc();
objHello.func = userFunc;
objHello.call(); // (출력결과) Hello



function saySomething(obj, methodName, name) {
	return (function(greeting) {
		return obj[methodName](greeting, name);
	});
}

function newObj(obj, name) {
	obj.func = saySomething(this, "who", name);
	return obj;
}

newObj.prototype.who = function(greeting, name) {
	console.log(greeting + " " + (name || "everyone") );
}



var obj1 = new newObj(objHello, "zzoon");
obj1.call();

이벤트 핸들러 형식은 function(event) {}인데, event 외의 원하는 인자를 더 추가한 이벤트 핸들러를 사용하고 싶을때, 앞과 같은 방식으로 클로저를 적절히 활용해줄 수 있다.


함수의 캡슐화

var buffAr = [
	'I am ',
	'',
	'. I live in',
	'',
	'. I\'am ',
	'',
	' yars old.'
];

function getCompletedStr(name, city, age) {
	buffAr[1] = name;
	buffAr[3] = city;
	buffAr[5] = age;

	return buffAr.join('');
}

var str = getCompletedStr('zzoon', 'seoul', 16);
console.log(str);

buffAr이 전역변수로서 외부에 노출되어 있기 때문에, 클로저를 이용해(buffAr을 추가적인 스코프에 넣는) 이 문제를 해결할 수 있다.

var getCompletedStr = (function() {  
  
    var buffAr = [  
        'I am ',  
  '',  
  '. I live in',  
  '',  
  '. I\'am ',  
  '',  
  ' yars old.'  
  ];  
  
  return (function(name, city, age) {  
        buffAr[1] = name;  
  buffAr[3] = city;  
  buffAr[5] = age;  
  
  return buffAr.join('');  
  });  
})();  
  
var str = getCompletedStr('zzoon', 'seoul', 16);  
console.log(str);

setTimeout()에 지정되는 함수의 사용자 정의

function callLater(obj, a, b) {  
    return (function() {  
        obj["sum"] = a + b;  
  console.log(obj["sum"]);  
  });  
}  
  
var sumObj = {  
    sum : 0  
};  
  
var func = callLater(sumObj, 1, 2);  
setTimeout(func, 500);

setTimeout함수에 넣는 첫번째 매개변수에 인자를 넣을 수 없기 때문에, 클로저를 활용한다.


클로저를 활용할 때 주의사항

클로저의 프로퍼티값이 쓰기 가능하므로 그 값이 여러 번 호출로 항상 변할 수 있음에 유의해야 한다.

function outerFunc(argNum) {  
    var num = argNum;  
  
  return function(x) {  
        num += x;  
  console.log('num: ' + num);  
  }  
  
}  
  
var exam = outerFunc(40);  
exam(5); // 45  
exam(-10); // 35


하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 있는 경우도 있다

function func() {  
  
    var x = 1;  
  
  return {  
        func1 : function() { console.log(++x); },  
  func2 : function() { console.log (-x); }  
    };  
  
};  
  
var exam = func();  
exam.func1(); // 2  
exam.func2(); // -2


루프 안에서 클로저를 활용할 때는 주의하자

function countSeconds(howMany) {  
    for (var i = 1; i <= howMany; i++) {  
        setTimeout(function() {  
            console.log(i);  
  }, i * 1000);  
  }  
}  
  
countSeconds(3); // 4 4 4

setTimeout 함수가 실행되는 시점은 countSeconds() 함수의 실행이 종료된 이후이고, i 값은 이미 4가 된 상태이다.


해결법은 루프 i값 복사본을 넘겨 즉시 실행함수를 사용

function countSeconds(howMany) {  
    for (var i = 1; i <= howMany; i++){  
        (function(currentI) {  
            setTimeout(function() {  
                console.log(currentI);  
  }, currentI * 1000);  
  })(i);  
  }  
}  
  
countSeconds(3);


'WEB(웹) > javascript' 카테고리의 다른 글

클래스, 생성자, 메서드  (0) 2018.07.07
클래스 기반 언어와 프로토타입 기반 언어  (0) 2018.07.07
join(), split(), includes()  (0) 2018.07.04
replaceAll() 구현  (0) 2018.07.04
스코프 체인  (0) 2018.07.03