여러가지 클로저 활용사례

신은수·2023년 5월 18일
0

VanillaJS

목록 보기
10/11

1. 클로저란? (이전글 참고)

클로저란 어떤 함수A에서 선언한 변수a를 참조하는 내부함수B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수a가 사라지지 않는 현상

2. 클로저 활용사례

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

a) 콜백함수를 고차함수로 바꿔서 클로저를 활용한 방안

  • 고차함수 : 함수를 인자로 받거나 또는 함수를 반환함으로써 작동 하는 함수
var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');
var alertFruitBuilder = function (fruit) {
    return function () {
        alert('your choice is ' + fruit);
    }
}
fruits.forEach(function (fruit) {
    var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', alertFruitBuilder(fruit));
    $ul.appendChild($li);
})
document.body.appendChild($ul)

2) 정보은닉

  • 정보은닉: 어떤 모듈의 내부로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념
  • 자바스크립트는 기본적으로 변수 자체에 이러한 접근 권한을 직접 부여하도록 설계되어 있지 않음
  • 클로저를 이용하여 함수 차원에서 public한 값가 private한 값을 구분하는 것이 가능

3) 부분적용함수

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

a) 첫번째 예제 add와 addPartial

  • addPartial함수는 인자 5개를 미리 적용하고 추후 추가적으로 인자들을 전달하면 모든 인자를 모아 원래의 함수가 실행되는 부분적용함수

  • 하지만 this의 값을 변경할 수 없기 때문에 메서드에서는 사용할 수 없음

    var add = function () {
        console.log(this)
        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));
    
    var objA = {
                name: "a",
                addMethod: add
            }
    
    var objB = {
        name: "b",
        addMethod: addPartial
    }
    
    // 메서드로서 호출했을 때 this는 함수앞의 객체
    console.log(objA.addMethod(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 
    // 하지만 objB는 addPartial을 addMethod로 넣었기 때문에 -> Window 
    console.log(objB.addMethod(6, 7, 8, 9, 10))
  • 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 () {
        console.log(this);
        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));
    
    var objB = {
        name: "b",
        addMethod: addPartial
    }
    console.log(objB.addMethod(6,7,8,9,10));
    

b) 인자들을 원하는 위치에 미리 넣어놓고 나중에 빈자리에 인자를 채워넣어 실행하는 (클로저를 사용하는) 부분적용함수

Object.defineProperty(window, '_', {
    value: 'EMPTY_SPACE',
    writable: false,
    configurable: false,
    enumerable: false
})

var partial2 = 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);
        for (var i = 0; i < partialArgs.length; i++) {
            if (partialArgs[i] === _) {
                partialArgs[i] = restArgs.shift(); // 배열의 앞에서 원소를 빼냄
            }
        }
        console.log(partialArgs, restArgs);
        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 = partial2(add, 1, 2, _, 4, 5, _, _, 8, 9);
console.log(addPartial(3, 6, 7, 10));

c) (클로저를 사용하는) 디바운스 구현

  • 디바운스: 짧은 시간동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것
    • 최초 이벤트가 발생하면 timeoutId = setTimeout(func.bind(self,event), wait) 실행 -> 대기열에 'wait 시간 뒤에 func를 실행할 것'이라는 내용이 담기게 됨
    • 그런데 wait 시간이 경과하기 전에 다시 동일한 event가 발생하면, clearTimeoutId(timeoutId)로 앞서 저장했던 대기열을 초기화하고 새로운 대기열을 등록.
var debounce = function (eventName, func, wait) {
    var timeoutId = null;
    return function (event) {
        var self = this;
        console.log(eventName, 'event발생');
        clearTimeout(timeoutId);
        timeoutId = setTimeout(func.bind(self, event), wait);
    }
}

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

document.body.addEventListener('mousewheel', debounce('wheel', wheelHandler, 700));

4) 커링함수

  • 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인형태로 구성한 것

  • 커링은 한번에 하나의 인자만 전달하는 것을 원칙으로 함

  • 중간과정상의 함수를 실행한 결과는 그 다음 인자를 받기위해 대기만 할 뿐으로 마지막 인자가 전달되기 전까지는 원본함수가 실행되지 않음

  • 지연실행

    var getInformation = (baseUrl) => (path) => (id) => {
      console.log(baseUrl + path + '/' + id);
      return baseUrl + path + '/' + id;
    };
    
    var imageUrl = 'http://image.com/';
    var productUrl = 'http://product.com/';
    
    //이미지 타입별 요청 함수 준비
    var getImage = getInformation(imageUrl);
    var getEmoticon = getImage('emoticon');
    var getIcon = getImage('icon');
    
    //제품 타입별 요청 함수 준비
    var getProduct = getInformation(productUrl);
    var getFruit = getProduct('fruit');
    var getVegetable = getProduct('vegetable');
    
    //실제 요청
    var emoticon1 = getEmoticon(100);
    var emoticon2 = getEmoticon(102);
    var icon1 = getIcon(205);
    var icon2 = getIcon(227);
    var fruit1 = getFruit(300);
    var fruit2 = getFruit(400);
    var vegetable1 = getVegetable(901);
    var vegetable1 = getVegetable(942);
profile
🙌꿈꾸는 프론트엔드 개발자 신은수입니당🙌

0개의 댓글