\(@^0^@)/

[BOOK] 코어 자바스크립트, 클로저 2 본문

BOOKS/코어 자바스크립트

[BOOK] 코어 자바스크립트, 클로저 2

minjuuu 2022. 4. 4. 16:28
728x90

오늘 읽은 범위 : (클로저) p.125 ~ p.145


< 책에서 기억하고 싶은 내용 >

클로저

콜백 함수 내부에서 외부 데이터를 사용하고자 할 때

// 콜백 함수와 클로저

var fruits = ['apple', 'banana', 'peach'];
var $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);
    });
    $ul.appendChild($li);
});
document.body.appendChild($ul);
  • 콜백 함수 A는 내부에서 외부 변수를 사용하지 않고 있으므로 클로저가 없지만
  • 콜백 함수 B는 addEventListener에 fruit라는 외부 변수를 넘겨줘서 참조하고 있으므로 클로저가 있다.
  • B 함수가 참조할 예정인 변수 fruit에 대해서는 함수 A가 종료된 후에도 GC대상에서 제외되어 계속 참조 가능.

 

  • 하지만 B함수의 쓰임새가 콜백 함수에 국한되지 않는 경우라면 반복을 줄이기 위해 B를 외부로 분리하는 것이 나음.
  • bind 메서드를 활용하여 분리
var alertFruit = function (fruit) {
	alert('your choice is ' + fruit);
};

fruits.forEach(function (fruit) {
    var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', alertFruit.bind(null, fruit));
    $ul.appendChild($li);
});
document.body.appendChild($ul);
  • 고차 함수를 활용하여 분리
var alertFruit = function (fruit) {
	return function () {
    	alert('your choice is ' + fruit);
    }
};

fruits.forEach(function (fruit) {
    var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', alertFruit.bind(null, fruit));
    $ul.appendChild($li);
});
document.body.appendChild($ul);

클로저를 이용한 접근 권한 제어 (정보 은닉)

  • 정보 은닉은 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈 간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념 중 하나.
  • 자바스크립트는 기본적으로 변수 자체에 이러한 접근 권한을 직접 부여하도록 설계되어있지 않다.
    그렇다고 접근 권한 제어가 불가능한 것은 아니다.
    클로저를 이용하면 함수 차원에서 public 한 값과 private 한 값을 구분하는 것이 가능.
  • 외부에 제공하고자 하는 정보들을 모아서 return 하고, 내부에서만 사용할 정보들은 return하지 않는 것으로 접근 권한 제어가 가능하다.
    return 한 변수들은 공개 멤버(public member)가 되고, 그렇지 않은 변수들은 비공개 멤버(private member)가 된다.
  • 클로저를 활용해 접근권한을 제어하는 방법
    1. 함수에서 지역변수 및 내부 함수 등을 생성한다.
    2. 외부에 접근권한을 주고자 하는 대상들로 구성된 참조형 데이터(대상이 여럿일 때는 객체 또는 배열, 하나일 때는 함수)를 return 한다.
      ==> return 한 변수들은 공개 멤버가 되고, 그렇지 않은 변수들은 비공개 멤버가 된다.

부분 적용 함수

  • n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m) 개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수

 

  • 디바운스
    • 디바운스는 짧은 시간 동안 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것으로 성능 최적화에 큰 도움을 주는 기능.
    • 실무에서 부분 함수를 사용하기에 적합한 예.
    • scroll, wheel, mousemove, resize등에 적용하기 좋다.
var debounce = function (eventName, func, wait) {
    var timeoutId = null;
    return function (event) {
        var self = this;
        conosle.log(eventName, 'event 발생');
        clearTimeout(timeoutId);
        timeoutId = setTimeout(func.bind(self, event), wait);
    };
}

var moveHandler = function (e) {
    console.log('move event 처리');
};

var wheelHandler = function (e) {
    console.log('wheel event 처리');
}
document.body.addEventListener('mousemove', debounce('move', moveHandler, 500));
document.body.addEventListener('mousewheel', debounce('wheel', wheelHandler, 700));

 

  • 디바운스 함수는 eventName과 실행할 함수, 발생한 이벤트인지 여부를 판단하기 위한 대기시간(wait)을 받는다.
  • 내부에서는 timeoutId 변수를 생성하고, 클로저로 EventListener에 의해 호출될 함수를 반환한다.
  • 반환될 함수 내부에서는, setTimeout을 사용하기 위해 this를 별도의 변수에 담고, clearTimeout을 이용해 초기화.
  • setTimeout으로 wait 시간만큼 지연시킨 다음, 원래의 함수를 호출하는 형태.
  • 최초 이벤트가 발생하면 timeout의 대기열에 담긴다. 그런데 시간(wait)이 경과하기 이전에 다시 동일한 이벤트를 발생시킨다면, 저장했던 시간 대기열을 초기화하고 다시 새로운 대기열에 담는다.
    결국 각 이벤트가 바로 이전 이벤트로부터 시간(wait) 이내에 발생하는 한, 마지막에 발생한 이벤트만이 초기화되지 않고 무사히 실행된다.
  • 디바운스 함수에서 클로저로 처리되는 변수에는 eventName, func, wait, timeoutId가 있다.

커링 함수

  • 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것
  • 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 한다.
    또한 중간 과정상의 함수를 실행한 결과는 그다음 인자를 받기 위해 대기만 할 뿐으로, 마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않는다.
  • 부분 적용 함수는 여러 개의 인자를 전달할 수 있고, 실행 결과를 재실행할 때 원본 함수가 무조건 실행된다.
    부분 적용 함수와 달리 커링 함수는 필요한 상황에 직접 만들어 쓰기 용이하다.
// 커링 함수
var curry5 = function (func) {
    return function (a) {
        return function (b) {
            return function (c) {
                return function (d) {
                    return function (e) {
                    };
                };
            };
        };
    };
};

var getMax = curry5(Math.max);
console.log(getMax(1)(2)(3)(4)(5));

// 화살표 함수
var curry5 = func => a => b => c => d => e => func(a, b, c, d, e);
  • 화살표 함수로 구현하면 커링 함수를 이해하기에 훨씬 수월하다.
  • 화살표 순서에 따라 함수에 값을 차례로 넘겨주면 마지막에 func가 호출될 거라는 흐름이 한눈에 파악됨.

 

  • 지연 실행(lazy execution) : 당장 필요한 정보만 받아서 전달하고 또 필요한 정보가 들어오면 전달하는 식으로, 결국 마지막 인자가 넘어갈 때까지 함수 실행을 미루는 함수형 프로그래밍.
  • 원하는 시점까지 지연시켰다가 실행하는 것이 요긴한 상황이라면 커링을 쓰기에 적합하다.
var getInformation = function (baseUrl) {
    return function (path) {
        return function (id) {
            return fetch(baseUrl + path + '/' + id);
        };
    };
};

// ES6 화살표함수
var getInformation = baseUrl => path => id => fetch(baseUrl + path + '/' + id);
  • 프로젝트 내에서 자주 쓰이는 함수의 매개변수가 항상 비슷하고 일부만 바뀌는 경우에도 커링 함수에 적합하다.
  • 최근 여러 프레임워크나 라이브러리 등에서 커링을 상당히 광범위하게 사용하고 있다. 예) Redux의 미들웨어

클로저 정리

  • 클로저란 어떤 함수에서 선언한 변수를 참조하는 내부 함수를 외부로 전달할 경우,
    함수의 실행 콘텍스트가 종료된 후에도 해당 변수가 사라지지 않는 현상.
  • 내부 함수를 외부로 전달하는 방법에는 함수를 return 하는 경우뿐 아니라 콜백으로 전달하는 경우도 포함.
  • 클로저는 그 본질이 메모리를 계속 차지하는 개념이므로 더는 사용하지 않게 된 클로저에 대해서는 메모리를 차지하지 않도록 관리해줄 필요가 있다.

 


[ 출처 : 코어 자바스크립트 ]
[ 참고

]

 

728x90