봉봉의 개인 블로그
2019-02-13(JavaScript 클로저) 본문
클로저란?
흔히 함수 내에서 함수를 정의하고 사용하면 클로저라고 한다. 하지만 대개는 정의한 함수를 리턴하고 사용은 바깥에서 하게 된다.
코드는 아래와 같다.
1 2 3 4 5 6 7 8 | function getClosure() { var text = 'variable 1'; return function() { return text; }; } var closure = getClosure(); console.log(closure()); | cs |
위에서 정의한 getClosure()는 함수를 반환하고, 반환된 함수는 getClosure() 내부에서 선언된 변수를 참조하고 있다. 또한 이렇게 참조된 변수는 함수 실행이 끝났다고 해서 사라지지 않았고, 여전히 제대로ㅓ 된 값을 반환하고 있는 걸 알 수 있다.
여기서 반환된 함수가 클로저인데, MDN에서 정의된 내요에서도 말했든 환경을 기억하고 있는것처럼 보인다.
다른예제를 살펴 보면
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var base = 'Hello, '; function sayHelloTo(name) { var text = base + name; return function() { console.log(text); } } var hello1 = sayHelloTo('1번째'); var hello2 = sayHelloTo('2번째'); var hello3 = sayHelloTo('3번째'); hello1(); // Hello, 1번째 hello2(); // Hello, 2번째 hello3(); // Hello, 3번째 | cs |
출력된 결과를 보면 text 변수가 동적으로 변화하고 있는 것처럼 보인다. 실제로는 text 라는 변수 자체가 여러번 생성된 것이며, hello1(), hello2(), hello3() 은 서로 다른 환경을 가진것이다.
클로저를 통한 은닉화
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function Hello(name) { this._name = name; } Hello.prototype.say = function() { console.log('Hello, ' + this._name); } var hello1 = new Hello('객체1'); var hello2 = new Hello('객체2'); var hello3 = new Hello('객체3'); hello1.say(); // 'Hello, 객체1' hello2.say(); // 'Hello, 객체2' hello3.say(); // 'Hello, 객체3' hello1._name = '객체4'; hello1.say(); // 'Hello, 객체4' | cs |
위에서 Hello()로 생성된 객체들은 모두 _name 이라는 변수를 가지고 된다. 변수명 앞에 underscore(_)를 포함했기 때문에 일반적인 JavaScript 네이밍 컨벤션을 생각해보면 이 변수는 Private variable으로 쓰고 싶어한 것을 알수 있다. 하지만 실제로는 여전히 외부에서 쉽게 접근이 가능한 변수이다.
이 경우에 클로저를 사용하여 외부에서 변수에 직접 접근하는 것을 제한할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function Hello() { var _name = name; return function(){ console.log('Hello, ' + _name); } } var hello1 = Hello('객체1'); var hello2 = Hello('객체2'); var hello3 = Hello('객체3'); hello1(); // 'Hello, 객체1' hello2(); // 'Hello, 객체2' hello3(); // 'Hello, 객체3' | cs |
특별히 인터페이스를 제공하는 것이 아니라면, 여기서는 외부에서 _name에 접근할 방법이 전혀 없다. 이렇게 은닉화도 생각보다 쉽게 해결할 수 있다.
반복문 클로저
1 2 3 4 5 6 | var i; for(i = 0 ; i < 10 ; i++) { setTimeout(function() { console.log(i) },100); } | cs |
위 코드는 간단하게 0~9까지의 정소를 출력하는 코드이다. 하지만 실제로 돌려보면 엉뚱하게도 10이라는 숫자만 10번 출력되는 걸 볼 수 있다.
먼저 setTimeout() 에 인자로 넘긴 익명함수는 모두 0.1초 뒤에 호출될 것이다. 그 0.1초 동안에 이미 반복문이 모두 순회하면서 i값은 이미 10이 된 상태이고 그 때 익명함수가 호출되면서 이미 10이 되어버린 i를 참조하기 때문이다.
이 경우에도 클로저를 사용하면 원하는 대로 동작하도록 만들수 있다.
1 2 3 4 5 6 7 8 | var i; for(i = 0 ; i < 10 ; i++) { (funcion(j) { setTimeout(function() { console.log(j); },100); })(i); } | cs |
중간에 IIFE (즉시함수 호출방식)를 덧붙여 setTimeout() 에 걸린 익명 함수를 클로저로 만들었다. 앞에서 말한대로 클로저는 만들어진 환경을 기억하고 있다. 이 코드에서 i는 IIFE내에 j라는 형태로 주입되고, 클로저에 의해 각기 다른 환경속에 포함된다. 반복문은 10 회 반복되므로 10개의 환경이 생기고, 10개의 서로 다른 환경에 10개의 서로 다른 j 가 생기게 된다.
만약 IIFE 매개변수로 i를 넘기지 않고 그냥 직접 참조 하게 되면 정상적으로 동작하지 않는데, 그 이유는 인자로 i를 넘기지 않는다면 당연히 클로저가 참조하는 IIFE의 함수 스코프에서도 i값이 없으므로 생성 당시의 외부 스코프인 클로벌을 탐색하게 되고 결국 모두 같은 i 를 참조하게 된다. 반면에, 인자로 i 를 넘기게 되면 IIFE로 만든 10개의 스코프에 모두 i라는 변수가 다른 값으로 생기므로 정상적으로 동작할 수 있는것이다.
참고로 여기서 콜백으로 넘기는 함수 자체를 IIFE로 만들면 될꺼같지만 그렇지도 않다. 그렇게 만들게 되면 0~9까지의 숫자는 정상적으로 출력되지만 setTimeout()의 0.1초 딜레이가 작동하지 않게 된다. (함수가 즉시 실행되므로)
클로저의 성능
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function hello(name) { var _name = name; return function() { console.log('hello, ' + name) }; } var hello1 = new hello('객체1'); var hello2 = new hello('객체2'); var hello3 = new hello('객체3'); hello1(); // 'hello, 객체1' hello2(); // 'hello, 객체2' hello3(); // 'hello, 객체3' //여기서 메모리를 release 시키기 클로저의 참조를 제거 hello1 = null; hello2 = null; hello3 = null; | cs |
이처럼 메모리 관리에 있어서 약점이 존재한다.
출처 : https://hyunseob.github.io/2016/08/30/javascript-closure/
'입사후 공부한내용' 카테고리의 다른 글
2019-02-18(Spring DI Dependency Injection) (0) | 2019.02.18 |
---|---|
2019-02-13(JavaScript IIFE 란?) (0) | 2019.02.13 |
2019-02-11(Java Script 비동기 처리와 콜백함수) (0) | 2019.02.11 |
2018-12-18(JavaScript Hoisting 호이스팅) (0) | 2018.12.18 |
2018-09-18(JSTL의 기본적인 사용 문법) (0) | 2018.09.18 |