클로저란?

Sever/Node.js 2020. 6. 24. 14:45
반응형

https://hyunseob.github.io/2016/08/30/javascript-closure/

 

JavaScript 클로저(Closure)

클로저란?MDN에서는 클로저를 다음과 같이 정의하고 있다. 클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 ‘기억한다’. 흔히 함수 내��

hyunseob.github.io

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures

 

클로저

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

developer.mozilla.org

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getClosure() {
    var text = 'variable 1';
    return function() {
        return text;
    }
}
 
var closure = getClosure();
console.log(closure());
 
//클로저는 독립적인 (자유) 변수를 가르키는 함수이다.
//클로저 안에 정의된 함수는 만들어진 환경을 기억한다
 
//흔히 함수내에서 함수를 정의하면 클로저라 한다
//대게는 정의한 함수를 리턴하고 사용은 바깥에서 하게 된다.
 
//getClosure()는 함수를 반환하고 반환된 함수는 getClosure()내부에서 선언된 변수 (text)를 참조 하고있다.
//이렇게 참조된 변수는 함수 실행이 끝났다고 해서 사라지지 않고 여전히 제대로 된 값을 반환 하고 있다.
 
//여기서 반환된 함수가 클로저다.
 
//참조: https://hyunseob.github.io/2016/08/30/javascript-closure/
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"use strict"
 
console.log("strict mode");
 
let base = 'Hello, ';
 
function sayHelloTo(name) {
    let text = base + name;
    return function() {
        console.log(text);
    }
}
 
let hello1 = sayHelloTo("hong");
let hello2 = sayHelloTo("lim");
let hello3 = sayHelloTo("kim");
 
hello1();
hello2();
hello3();
 
//출력된 결과를 보면 text 변수가 동적으로 변화 하고 있는 것처럼 보인다.
//실제로는 text라는 변수자체가 여러번 생성되는 것이다.
//즉, hello1(), hello2(), hello3()는 서로 다른 환경을 가지고있는것이다.
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//this test
/*
function Hello(name) {
    console.log('2. ', this); //1.  {}
}
 
console.log('1. ', this); // <ref *1> Object [global]
Hello();
*/
 
function Hello(name) {
    this._name = name;
}
 
Hello.prototype.say = function() {
    console.log('Hello, ' + this._name);
}
 
let hello1 = new Hello('hong');
let hello2 = new Hello('kim');
let hello3 = new Hello('lee');
 
hello1.say();
hello2.say();
hello3.say();
 
hello1._name = 'anonymous';
hello1.say();
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 
function hello(name) {
    var _name = name;
    return function() {
        console.log('Hello, ' + _name);
    }
}
 
let hello1 = hello('hong');
let hello2 = hello('kim');
let hello3 = hello('lee');
 
hello1();
hello2();
hello3();
 
 
 
 
//클로저를 통한 은닉화 
//일반적으로 JavaScript에서 객체지향 프로그래밍을 말한다면 Prototype을 통해 객체를 다루는 것을 말한다.
//Prototype을 통해 객체를 만들때 주요한 문제중 하나는 Private variable에 대한 접근 권한 문제다 
 
//Hello()로 생성된 객체들은 모두 _name이라는 변수를 가지게 된다.
//변수명앞에 underscore(_)를 포함했기 때문에 일반적인 JavaScript네이밍 컨벤션을 생각해 봤을때 이변수는 Private variable로 쓰고 싶다는 의도임을 알수있다.
//하지만 실제로는 여전히 외부에서도 접근가능한 변수이다.
 
//이경우 클로저를 사용하여 외부에서 변수에 직접 접근 하는것을 제한 할수 있다.
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//반복문 클로저 
var i;
for (i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 100);
}
 
//0-9까지 정수를 출력 하는 코드, 실제로 돌려보면 10만 찍힘 
//setTimeout()에 인자로 넘긴 익명함수는 모두 0.1초 뒤에 호출될것임
//그 0.1초동안 이미 반복문이 모두 순회 되면서 i의 값은 이미 10이 된상태.
//그때 익명함수가 호출되면서 10이 되어 버린 i를 참조 하는것임 
 
//클로저로 해결 가능 
 
var j;
for (j = 0; j < 10; j++) {
    (function(k) {
        setTimeout(function() {
            console.log(k);
        }, 100);
    })(j);
}
 
/*IIFE(Immediately Invoked Function Expressions: “Iffy”라고 발음)는 즉시 호출 함수 표현식의 줄임말입니다. 기본적인 형태는 다음과 같습니다. 이것은 즉시 호출되는 익명 함수 표현식 입니다.*/
 
//중간에 IIFE를 붙여 setTimeout()에 걸린 익명함수를 클로저로 만들었다.
//클로저는 만들어진 환경을 기억 한다.
//j는 IIFE내에 k라는 형태로 주입되고 클로저는 각기 다른 환경속에 포함된다.
//반복문은 10회 반복 되므로 10개의 환경이 생길 것이고 10개의 서로 다른 환경에 10개의 서로다른 k가 생기는 것이다.
cs

IIFE 매개변수로 j를 넘기지 않고 그냥 직접 참조 해도 되지 않느냐는 의문이 들수도 있다.

IIFE를 통해 클로저 마다 환경이 생긴다.

하지만 인자로 j를 넘기지 않는다면 클로저가 참조하는 IIFE의 함수 스코프도 j값이 없으므로 생성 당시 외부 스코프인 글로벌을 탐색하게 되고 결국 모두 같은 j를 참조 하게 된다.

반면에 j를 인자로 넘기게 되면 IIFE로 만든 10개의 스코프에 모두 j라는 변수가 다른 값으로 생기므로 정상동작 하는것이다.

콜백으로 넘기는 함수 자체를 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
20
21
22
23
24
25
26
27
28
29
30
//클로저의 성능 
 
//클로저는 각자의 환경을 가진다.
//이 환경을 기억하기 위해서 당연히 메모리가 소모 된다.
//클로저를 생성해 놓고 참조를 제거 하지 않는것은 c++에서 동적 할당을 객체로 생성해놓고 delete를 사용하지 않는것과 비슷 하다.
//클로저를 통해 내부변수를 참조 하는 동안 내부 변수가 차지 하는 메모리를 GC는 회수하지 않는다.
//따라서 클로저 사용이 끝나면 반드시 참조를 제거 하는것이 좋다.
 
function hello (name){
    var _name = name;
    return function (){
        console.log('Hello ' + _name);
    };
}
 
var hello1 = hello('hong');
var hello2 = hello('lee');
var hello3 = hello('kim');
 
hello1();
hello2();
hello3();
 
//여기서 메모리를 release 시켜야 클로저의 참조가 제거 된다.
hello1 = null;
hello2 = null;
hello3 = null;
 
//이처럼 메모리 관리에 있어서 약점이 있지만 추가로 스코프 체인을 검색하는 시간과 새로운 스코프를 생성하는데 드는 비용도 감안하지 않을수 없다.
 
cs
반응형

'Sever > Node.js' 카테고리의 다른 글

Node.js & Redis Cache  (0) 2020.07.28
클래스 구문  (0) 2020.06.24
Node.js: Hello로 시작하는 Web 애플리케이션  (0) 2020.06.24
Node 다른 서버 API 호출  (0) 2020.06.11
node.js socket.io  (0) 2019.08.09
: