본문 바로가기

Language/Java Script

자바스크립트 클로저

1. 클로저의 개념

  - 이미 생명 주기(실행 컨텍스트)가 끝낸 외부 함수의 변수를 참조하는 함수를 클로저라고 한다.

function outerFunc(){
	var x = 10;
    var innerFunc = function(){console.log(x);}
    return innerFunc;
}

var inner = outerFunc();
inner(); //10

 · innerFunc가 클로저이다.

 · x : 자유변수(free variable)

 · 클로저란 자유변수와 엮여있다 란 의미

 

이해 확인

function outerFunc(arg1, arg2){
	var local = 8;
    function innerFunc(innerArg){
    	console.log((arg1+arg2)/(innerArg + local));
    }
    return innerFunc;
}
var exam1 = outerFunc(2, 4);
exam1(2); //출력 결과 : 6/10

  · outerFunc의 변수가 innerFunc의 [[scope]]로 참조되므로 가비지 컬렉션의 대상이 되지 않고, 여전히 접근 가능

  · 클로저를 많이 사용하면 callstack이 쌓이므로 메모리 부족 및 성능저하의 원인이 될 수 있다. 적절한 사용 필요.

 

2. 클로저의 활용

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

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

HelloFunc.prototype.call = function(func){
	func ? func(this.greeting) : this.func(this.greeting);
}

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(); // 출력 결과 : hello zzoon

  2) 함수의 캡슐화

    "I am XXX, I live in XXX, I'am XX years old" 라는 문장을 출력하는데 XX 부분은 사용자로부터 인자로 입력받아 값을 출력하는 함수

 

var buffAr = [
	'I am ',
    '',
    '. I live in ',
    '',
    '. I\'am ',
    '',
    ' years 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);

  buffArr은 전역 변수로서 외부에 노출되어 있다. 값이 변경될 수 있고, 같은 이름의 변수로 버그가 생길 수 있다.

 

  클로저를 이용한 개선 방법

var getCompletedStr = (function(){
	var buffAr = [
    	'I am ',
        '',
        ', I live in ',
        '',
        ', I\'am ',
        '',
        ' years 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);

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

  - setTimeout함수는 웹 브라우저에서 제공하는 함수인데 첫 번째 인자로 넘겨지는 함수 실행의 스케쥴링을 할 수있다.

    두 번째 인자인 밀리 초 단위 숫자만큼의 시간 간격으로 해당 함수를 호출한다.

    자기 자신(this)를 넘겨 주려면 어떻게 해야할까?

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);

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

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

function outerFunc(argNum){
	var num = argNum;
    return function(x){
    	num += x;
        console.log('num: ' + num);
    }
}
var exam = outerFunc(40);
exam(5);
exam(-10);
//exam의 값을 호출할 때마다 자유변수 num의 값은 계속 변한다.

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

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

var exam = func();
exam.func1();
exam.func2();
//각각의 함수가 호출될때마다 x의 값이 변화한다.

3)루프 안에서 클로저가 활용될 때는 주의하자

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

올바르게 수정하기

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

countSeconds(3);