클로저에대해 여러책에서 한 문장으로 요약한 부분을 소개하면 다음과 같다!
🐕
- 자신을 내포하는 함수의 컨텍스트에 접근할 수 있는 함수
- 함수가 특정 스코프에 접근할 수 있도록 의도적으로 그 스코프에서 정의하는 것
- 함수를 선언할 때 만들어지는 유효범위가 사라진 후에도 호출할 수 있는 함수
- 이미 생명 주기상 끝난 외부 함수의 변수를 참조하는 함수자유 변수가 있는 함수와
자유변수를 알 수 있는 환경의 결함로컬 변수를 참조하고 있는 함수 내의 함수|- 자신이 생성될 때의 스코프에서 알 수 있었던 변수들 중 언젠가 자신이 실행될 때
사용할 변수들만을 기억하여 유지시키는 함수
MDN(Mozilla Developer Networ)에서는 클로저에 대해 아래와 같이 소개하고 있다.
”A closure is the combination of a function and the lexical environment within which that function was declared. “
(클로저는 함수와 그 함수가 선언될 상시의 lexical environment의 상호관계에 따른 현상)
여기서 잠깐 !
lexical enviroment 기억 잘 안나지...? 나만그래?
🐕lexical environment
- 컨텍스트를 구성하는 환경정보들을 사전에서 접하는 느낌적 느낌으로 모아놓은것 !
🐕environmentRecord와 호이스팅
- 현재 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어 나가며 순서대로 수집
지금까지 파악한 내용에 따르면 클로저란 “어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상” 이라고 볼 수 있겠쥬..?
아직은 잘 모르겠으니 실제 예제를 통해서 알아보자
외부함수에서 변수를 선언하고 내부함수에서 해당 변수를 참조하는 형태의 간단한 코드를 작성해 보았다.
var outer = function() {
var a = 1;
var inner = function() {
console.log(++a);
};
inner();
}
outer(); //2
outer
에 변수 a를 선언했고, outer
의 내부함수인 inner
함수에서 a의 값을 1만큼 증가 시킴.
inner
함수 내부에서는 a를 선언하지 않았기 때문에 environmentRecord에서 값을 찾지 못함.
상위 컨텍스트인 outer
의 lexicalEnvironment에서 접근해 a를 찾기 때문에 2가 출력된다.
그림 5-1 일반적인 상황에서의 콜스택 흐름
var outer = function() {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer(); //outer()가 종료된 이후에도 내부변수 a가 살아남아 계속 증가된다!
console.log(outer2()); //a:2
console.log(outer2()); //a:3
이번에도 동일하게 inner함수의 내부에서 외부변수인 a를 사용했다.
그림5-2 클로저 발생 시의 콜스택 흐름
{ outer1:f }
와 { outer2:undefined }
수집return inner;
가 되어 outer2에 담긴다.{ a: 1, ~~inner: f~~ }
{ outer1:f, outer2: inner }
가 담김⇒ outer environmentRecord에 있는 { a: 3 }
이 사라지지 않고 계속 남아있는 상태를 클로저라고 한다!!
“ 컨텍스트A에서 선언한 변수를 내부함수B에서 참조할 경우에 발생하는 특별한 현상”
이렇게 되면...
지역변수가 함수 종료 이후에도 사라지지 않게 할 수 있다?!!@!! (사용자가 선택적으로..)
클로저 활용 사례를 살펴보면서 클로저에 대해서 더 알아보자
var fruits = ['apple','banana','peach'];
var $ul = document.createElement('ul'); //(공통코드)
fruits.forEach(function (fruit) {
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', function () {
alert('your choice is ' + fruit);
});
$ul.appendChild($li);
});
document.body.appendChild($ul);
2) 접근 권한 제어(정보은닉)
정보은닉이란?
어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화 해서 모듈간의 결합도를 낮추고 유연성을 높히는 방법. 흔히 접근권한에는
public
,private
,protected
의 세 종류가 있다.
public
: 외부에서 접근 가능한 것
private
: 내부에서만 사용하며 외부에는 노출되지 않는 것
protected
: 동일패키지에 속하는 클래스와 하위클래스 관계에서 접근 가능
자바스크립트는 기본적으로 변수 자체에 접근권한을 직접 부여하도록 설계되어 있지 않다.
하지만 클로저를 이용한다면 가능해!
다음 코드에서 클로저로 public한 값과 private한 값을 구분하는것을 살펴보자
var outer = function() {
var a = 1;
var inner = function () {
return ++a;
};
return inner;
};
var outer2 = outer(); //outer()가 종료된 이후에도 내부변수 a가 살아남아 계속 증가된다!
console.log(outer2()); //a:2
console.log(outer2()); //a:3
✔ 클로저를 활용해 접근권한을 제어하는 방법은 아래와 같다!
- 함수에서 지역변수 및 내부함수 등을 생성한다.
- 외부에서 접근권한을 주고자 하는 대상들로 구성된 참조형 데이터(대상이 여럿일때는 객체 또는 배열, 하나일 때는 함수)를 return한다.
→ return한 변수들은 공개멤버가 되고, 그렇지 않은 변수들은 비공개 멤버가 됨.
3) 부분 적용 함수
부분적용함수란?
n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억 시켰다가 나중에 (n-m)개의 인자를 넘기면 비로소 원래의 함수 실행 결과를 얻을 수 있게끔 하는 함수이다.
this를 바인딩 해야 하는 점을 제외하면 앞서 살펴본 bind 메서드의 실행결과가 바로 부분적용 함수!
🐕 잠깐 bind()
가 뭐였더라?
func.bind(contex)
를 호출하면 일반적으로 래핑된 함수가 호출된다!
바인딩한 함수는 원본객체를 감사는 함수가 되지롱
또 초기 인수가 있는 함수가 있다면 제공된 this값을 따르고 바인딩 된 함수에 전달되어 바인딩 된 함수가 호출될 때마다 대상 함수의 인수앞에 삽입된다.
var add = function() {
var result = 0;
for(var i = 0; i < arguments.length; i++){
result += arguments[i];
}
return result;
};
var addPartial = add.bind(null, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); //55
addPartial
함수에서 인자 5개를 미리 적용하고, 추후 추가적으로 인자들을 전달하면 모든 인자를 모아 함수가 실행되는 부분 적용 함수이다.
그러나 위 코드는 this의 값을 변경할 수 밖에 없기 때문에 메서드에는 사용하기가 어렵다.
아래처럼 구현해보자!
var partial = function() {
var originalPartialArgs = arguments;
var func = originalPartialArgs[0];
if(typeof func !== 'function'){
throw new Error('첫 번째 인자가 함수가 아닙니다.');
}
return function() {
var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
var restArgs = Array.prototype.slice.call(arguments);
return func.apply(this, partialArgs.concat(restArgs));
};
};
var add = function() {
var result = 0;
for (var i = 0; i < arguments.length; i++){
result += arguments[i];
}
return result;
};
var addPartial = partial(add, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); //55
var dog = {
name : '강아지',
greet : partial(function(prefix, suffix) {
return prefix + this.name + suffix;
}, '왈왈, ' )
};
dog.greet('입니다!');
4) 커링함수
커링함수란?
여러개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게
체인형태로 구성한 것을 말한다.
앞서 살펴본 부분 적용 함수와 맥락은 똑같지만 몇가지 다른점이 있다.
- 커링은 한번에 하나의 인자만 전달하는것을 원칙으로 한다.
- 또한 중간과정에 함수를 실행한 결과는 그 다음 인자를 받기위해 대기만 할 뿐이므로
마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않는다.
⇒ 부분 적용 함수는 여러개의 인자를 전달할 수 있고 실행결과를 재실행 할때 원본함수가 무조건 실행된다.