만자의 개발일지

[JavaScript] 클로저(closure)란 본문

JavaScript

[JavaScript] 클로저(closure)란

박만자 2022. 3. 17. 14:15

클로저는 함수와 함수가 선언된 어휘적 환경(Lexical enviroment)의 조합입니다. 클로저를 이해하기 위해서는 먼저 

어휘적 범위 지정(Lexical scoping)에 대해 이해할 필요가 있습니다.

 

어휘적 범위 지정(Lexical scoping)

스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언했냐에 따라 결정됩니다. 이를 어휘적 범위 지정(Lexical scoping)이라 합니다.

 

예제를 통해 살펴보도록 하겠습니다.

 

outerFunc()(외부함수)는 지역 변수 name과 함수 innerFunc()을 생성합니다. 

innerFunc()은 outerFunc()안에서 정의된 내부 함수 입니다.

innerFunc()을 보면 외부함수인 outerFunc()에 정의된 지역변수에 접근할 수 있는 것을 보실 수 있습니다.

실행해보면 innerFunc()이 outerFunc()에 정의된 name의 값을 성공적으로 출력하게 됩니다.

function outerFunc() {
    let name = 'manja'

    function innerFunc() {
        console.log(name)
    }

    innerFunc()

}

outerFunc()
manja

자바스크립트는 위 코드를 다음과 같은 순서로 실행합니다.

  1. innerFunc() 내에서 변수 name을 검색한다.
  2. 위 단계에서 실패할 경우 outerFunc()(외부함수)의 스코프에서 변수 name을 검색한다.
  3. 위 단계에서 실패할 경우 전역 스코프에서 변수 name을 검색한다.

위 코드가 동작하는 이유는 innerFunc()이 outerFunc()의 내부에서 선언 되어서 innerFunc()의 상위 스코프가 outerFunc()이 되었고 그 안에 변수 name이 정의돼 있기 때문입니다.

 

클로저(closure)

클로저 는 함수와 어휘적 환경(Lexical enviroment)의 조합이라 했습니다.

어휘적 환경(Lexical enviroment)이란 쉽게 말해 내부 함수가 선언됐을 때의 스코프를 의미합니다.

때문에 내부 함수자체도 어휘적 환경(Lexical enviroment)에 포함된다고 말할 수 있습니다.

 

클로저의 특징은 다음과 같습니다.

  • 외부함수 스코프에서 내부함수 스코프로 접근 불가능하다.
  • 반대로 내부함수는 외부함수 스코프에 접근이 가능하다.
  • 외부함수의 실행이 종료된 후에도, 클로저는 외부함수의 스코프, 다시 말해 어휘적 환경(Lexical enviroment)에 접근할 수 있습니다.

 

어떻게 클로저는 외부함수의 실행이 종료된 후에도 어휘적 환경에 접근할 수 있을까요?

이경우에는 두 가지 방법이 있습니다.

 

첫 번째 방법은 외부함수가 클로저를 반환하는 경우입니다.

function outerFunc() {
    let a = 10

    return function(value) {
        return a += value
    }

}

let closure = outerFunc()
console.log(closure(5))
console.log(closure(5))
console.log(closure(5))
15
20
25

 

 

두 번째 방법은 전역으로 선언한 변수에 클로저를 할당하는 방법입니다.

let closure

function outerFunc() {
    let a = 10

    closure = function(value) {
        return a += value
    }

}

outerFunc()
console.log(closure(5))
console.log(closure(5))
console.log(closure(5))
15
20
25

 

클로저 언제 쓰나?

클로저는 자신이 생성될 때의 어휘적 환경(Lexical enviroment)를 기억해야 하기 때문에 메모리적으로 봤을 때 비효율적일 수도 있습니다. 그럼에도 불구하고 클로저를 사용하는 이유는 그만큼 강력하기 때문입니다.

어떤 상황에서 유용하게 사용되는지 살펴봅시다.

 

상태 유지

클로저는 현재 상태를 기억하고 변경된 최신 상태를 유지해야하는 상황일때 가장 유용하게 사용됩니다.

let changeState = (function() {
    let state = false

    return function() {
        return state = !state
    }
}())

console.log(changeState())
console.log(changeState())
console.log(changeState())
true
false
true

 

전역 변수의 사용 억제

클로저를 사용하면 전역 변수의 사용을 억제 할 수 있습니다.

아래 코드는 count변수를 전역 변수로 선언하고 increase() 함수를 통해 1씩 증가시키는 코드입니다.

물론 잘 동작하지만 전역 변수의 특징상 다른 모든 함수역시 전역 변수에 접근할 수 있기 때문에 의도치 않게 값이 변경될 수 있습니다. 

클로저를 사용하면 count를 increase() 함수의 지역 변수로 선언하여 의도치 않은 상태 변경을 방지할 수 있습니다.

let count = 0

function increase() {
    return ++count
}

console.log(increase())
console.log(increase())
console.log(increase())
1
2
3

 

클로저를 사용해 위 코드를 변환해 보았습니다.

클로저를 사용하면 불필요한 상태변경을 최대한 억제할 수 있어 결과적으로 서비스의 안전성이 올라가게됩니다.

let increase = (function() {

    let count = 0
    
    return function () {
        return ++count
    }
}())



console.log(increase())
console.log(increase())
console.log(increase())
1
2
3

 

정보의 은닉

this에 바인딩 되지 않은 변수들을 클로저를 통해 반환함으로써 자바의 private 키워드 처럼  클로저를 사용하면 이를 흉내낼 수 있습니다.

function Student() {
    let name = 'manja'
    let age = 19

    this.getName = function() {
        return name
    }

    this.getAge = function() {
        return age
    }
}

let manja = new Student()
console.log(manja.getName())
console.log(manja.getAge())

 

참고

 

Comments