라고 다양한책에서 말하고 있습니다.
클로저는 함수와 그함수가 선언될 당시의렉시컬 환경의 상호관계에 따른현상 - MDN
여기서 선언될 당시의 렉시컬 환경은 outerEnvironmentReference 에 해당합니다.
렉시컬환경은 outerEnvironmentReference 과 environmentRecord 에 의해 변수의 유효범위인 스코프가 결정되고 스코프 체인이 가능합니다.
(1) 외부 함수의 변수를 참조하는 내부 함수
let outer = function () {
let a = 1;
let inner = function () {
console.log(++a); // 2
};
inner();
};
outer();
inner 함수 내부에서는 a를 선언하지 않았기 때문에 environmentRecord 에서 값을 찾지못하므로 outerEnvironmentReference 에 지정된 상위 컨텍스트인 outer 의 LexicalEnvironment 에 접근하여 a를 찾습니다.outer 의 실행 컨텍스트가 종료되면 LexicalEnvironment 의 식별자들 (a, inner) 참조를 지웁니다. ⇒ 참조가 없어지므로 가비지 컬렉터 수집 대상이 됩니다.(2)외부 함수의 변수를 참조하는 내부 함수
let outer = function () {
let a = 1;
let inner = function () {
return ++a;
};
return inner; // inner 함수 자체를 반환
};
let outer2 = outer();
console.log(outer2); // 2
console.log(outer2); // 3
inner 함수는 outer 함수의 내부에 선언되었으므로 outer 함수의LexicalEnvironment 가 담길 것입니다. 스코프 체인을 통해서 outer 에서 선언한 변수 a 에 접근해서 1만큼 증가 시킬것입니다.하지만 inner 함수의 실행 시점에서 이미 outer 함수는 이미 실행 종료된 상태인데 어떻게 outer 함수의 LexicalEnvironment 에 접근할 수 있을까요?
바로
가비지 컬렉터의 동작 방식 때문입니다!
가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 수집대상에 포함시키지 않기 때문입니다.
그래서 다음과 같이 outer 의 실행이 종료 되더라도 inner함수는 outer2 가 참조하고 있으므로 가비지 컬렉터 수집대상에서 제외 되는 것입니다!
클로저 정리
클로저란?
어떤함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우
A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상!
GC가 수거할것이고 메모리는 회수 될것입니다.식별자를 참조형이 아닌 기본형 데이터(null이나 undefined)를 할당하면 됩니다.
코드예시
let outer = (function () {
let a = 1;
let inner = function () {
return ++a;
}
return inner;
})();
console.log(outer());
console.log(outer());
outer = null; // outer 식별자의 inner 함수 참조를 끊음
1. 콜백 함수 내부함수로 선언해서 외부변수를 직접 참조
const fruits = ['apple', 'banana', 'peach'];
const $ul = document.createElement('ul'); // (공통 코드)
fruits.forEach(function (fruit) { // (A)
var $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', function() {// (B)
alert('your choice is ' + fruit); // 클로저 fruit 외부 변수 참조
});
$ul.appendChild($li);
});
document.body.appendChild($ul);
forEach 를 사용하여 fruits의 개수만큼 실행 컨텍스트 생성outerEnvironmentReference가 A의 LexicalEnvironment를 참조하게 됩니다. 따라서 B가 참조할 예정인 변수 fruit에 대해서는 A가 종료된 후에도 GC대상에서 제외되고, 계속 참조가 가능합니다.2. bind 메서드를 활용한 클로저
- bind를 사용하여 함수의 this와 인자를 고정한 후, 클로저를 사용하지 않고 변수에 접근할 수 있습니다.
var alertFruit = function (fruit) {
alert('your choice is ' + fruit);
}
fruits.forEach(function (fruit) {
const $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', alertFruit.bind(null, fruit));
$ul.appendChlid($li);
});
document.body.appendChild($ul);
alertFruit.bind(null, fruit)를 호출하면, alertFruit의 this를 null로 설정하고, 첫 번째 인자로 fruit을 고정한 새로운 함수가 반환됩니다.addEventListener의 핸들러로 등록이 되고 클릭시 alertFruit이 실행되며 fruit이 전달이 됩니다.this가 원래의 this와 달라지기때문에 다른방식으로 해야합니다.3. 고차함수 사용
const alertFruitBuilder = function (fruit) {
return function () {
alert('your choice is ' + fruit);
};
};
fruits.forEach(function (fruit) {
const $li = document.createElement('li');
$li.innerText = fruit;
$li.addEventListener('click', alertFruitBuilder(fruit));
$ul.appendChild($li);
});
alertFruitBuilder라는 함수 내부에서는 익명함수를 반환합니다.alertFruitBuilder의 인자로 넘어온 fruit를 outerEnvironmentReference에 의해 참조할 수 있게 됩니다.alertFruitBuilder의 실행 결과로 반환된 함수에는 클로저가 존재하게 됩니다.public : 외부에서 접근 가능private: 내부에서만 사용하며 외부에 노출 않되는 것이러한 부분을 자바스크립트에서는 직접 부여하지 못하기 때문에 클로저를 이용하면 구분이 가능합니다!
const outer = function () {
let a = 1;
const inner = function () {
return ++a;
};
return inner;
};
const outer2 = outer();
console.log(outer2());
console.log(outer2());
outer라는 변수를 통해 outer함수를 실행은 가능하지만 outer함수 내부에는 개입이 불가능합니다!outer함수가 return한 정보에만 접근할 수 있는 것입니다.return하고return 하지 않는 것으로 접근 권한 제어가 가능한 것입니다.return한 변수들은 public 이되고, 그렇지 않으면 private가 되는 것입니다.object.freeze() 를 사용해서 접근권한은 제어할 수 있습니다.n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가
(n-m)개의 인자를 넘겨서 원하는결과 얻게 하는 방법입니다.
코드예시
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('입니다!'); // 왈왈, 강아지입니다.
커링함수란 여러개읜 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한것
- 커링은 한번에 하나의 인자만 전달하는 것을 원칙으로 합니다.
- 중간 과정상 함수를 실행한 결과는 그다음 인자를 받기 위해 대기만 할 뿐으로 마지막 인자가 전달되기 전까지는 원본 함수가 실행 되지 않습니다.
코드 예시
var curry3 = function(func) {
return function(a) {
return function(b) {
return func(a, b);
};
};
};
var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8)); // 10
console.log(getMaxWith10(25)); // 25
var getMinWith10 = curry3(Math.min)(10);
console.log(getMinWith10(8)); // 8
console.log(getMinWith10(25)); //10
화살표함수를 사용하여 해결할 수 있습니다.