[JS] 왜 closure가 중요할까?

Goni·2022년 12월 7일
0

Closure

lexical scope

javascript는 lexical scope언어이다.
즉, 자신이 정의된 위치에 따라 scope가 결정된다.

여기서 중요한건 함수는 언제 어디서 호출될 지 모르기 때문에 자신이 정의된 환경을 기억한다.
정의 된 환경은 상위 스코프에 해당한다.

Q. 왜 상위 스코프가 저장될까?

해당 함수가 정의될 때 실행되는 context는 상위 스코프의 context이기 때문이므로
내부 슬롯 [[Environment]]에 해당 스코프를 기억한다!

예제)

const x = 1;

function foo() {
	const x = 10;
    
    // 상위 스코프는 함수 정의 환경(위치)에 따라 결정
    // 함수 호출 위치와 상위 스코프는 무관
    bar();
}

// 정의된 위치의 스코프(현재 위치 전역)를 [[Environment]] 내부 슬롯에 저장하여 기억
function bar() {
	console.log(x);
}

foo(); // 1
bar(); // 1




Closure 정의

외부함수보다 중첩함수가 더 오래 존재하는 중첩함수
외부함수는 반환됐지만, 아직 중첩함수의 환경은 남아있는 것!

왜?
외부함수가 제거되어도 외부함수에 대한 lexical 환경은 유지되기 때문!
중첩함수가 어딘가의 내부슬롯에서 참조되어 있다면 GC의 대상이 되지 않는다.
그래서 외부함수의 환경이 유지될 수 있는 것이다.

Q. 그럼 js 함수들은 모두 상위스코프를 기억하므로 클로저인가?

A. 몇가지 제외되는 경우가 있다.

  1. 상위 스코프의 식별자를 참조하지 않는 함수
  2. 중첩 함수의 호출이 정의된 함수 내에서 끝나는 경우

이 두가지의 경우는 브라우저가 최적화를 위해 기억하지 않는다. 이로인해 메모리 낭비 예방 가능!

즉, 클로저는 중첩함수가 상위 스코프의 식별자를 참고하고, 외부함수보다 오래 유지되는 경우에 한정하는 것이 일반적이다.

자유변수란?

클로저에 의해 참조되는 상위스코프의 변수이다.
그래서 클로저를 이와 함께 직역해보면 자유변수에 묶여있는 함수라고도 한다.




Closure 활용

클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용한다.
즉, 상태의 의도치 않은 변경을 방지하기 위해 안전하게 은닉하고, 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.

// 카운트 상태 변경 함수
const increase = function () {
	// 카운트 상태 변수
    let num = 0;
    
    return ++num;
}

// 이전 상태 유지 못함
console.log(increase())	// 1
console.log(increase())	// 1
console.log(increase())	// 1

/* Closure 활용 */
const closureIncrease = (function () {
	let num = 0;
    
    return function() {
    	return ++num;
    }
}());

// 이전 상태 유지
console.log(closureIncrease())	// 1
console.log(closureIncrease())	// 2
console.log(closureIncrease())	// 3

이처럼 클로저는 상태(state)가 의도치 않게 변경되지 않도록 안전하게 은닉한다.
또한 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.
변수 값은 언제든지 변경될 수 잇어 오류 발생의 근본적 원인이 될 수 있다.
외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수 효과를 최대한 억제하여 오류를 피하고 안정성을 높이기 위해 클로저는 적극적으로 사용된다!




캡슐화와 정보 은닉

캡슐화

객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작하는 메서드를 하나로 묶는 것

캡슐화는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데 이를 정보 은닉이라 한다.

정보 은닉은 객체의 상태 변경을 방지해 정보를 보호하고, 객체 간 상호 의존성, 즉 결합도를 낮추는 효과가 있다.

JS는 public, private, protected와 같은 접근제한자를 제공하지 않는다.
즉, 객체의 모든 프로퍼티와 메서드는 public하다.

Q. 그럼 private는 어떻게 구현할까?

const Person = (function() {
	let _age = 0; // private
    
    // 생성자 함수
    function Person(name, age) {
    	this.name = name; // public
        _age = age;
    }
    
    // 프로토타입 메서드
    Person.prototype.sayHi = function() {
    	console.log(name, age);
    }
    
    // 생성자 함수 반환
   	return Person;
}

const me = new Person('Lee', 20);
me.sayHi(); // Lee 20
console.log(me.name); // Lee
console.log(me.age); // undefined

하지만, 위의 예제에서 new Person()의 객체를 여러개 생성했을 때 완벽한 은닉은 되지 않는다.
다른 인스턴스를 생성하더라도 sayHi는 하나의 동일한 상위 스코프를 사용하기 때문이다.
그래서 _age 변수의 상태가 유지되지 않는다.

현재 최신 브라우저에서는 private가 지원된다고 하니 참고하자.

0개의 댓글