[모던 JavaScript 튜토리얼] - [14.1] Proxy

IRISH·2024년 2월 10일
0

JS

목록 보기
70/80


학습 내용

  • Proxy : 특정 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체
    • 가로채진 작업은 Proxy 자체에서 처리되기도 하고, 원래 객체가 처리하도록 그대로 전달되기도함
  • 프락시는 다양한 라이브러리와 몇몇 브라우저 프레임워크에서 사용되고 있음

⇒ 문법

let proxy = new Proxy(target, handler)
  • target – 감싸게 될 객체로, 함수를 포함한 모든 객체가 가능합니다.
  • handler – 동작을 가로채는 메서드인 '트랩(trap)'이 담긴 객체로, 여기서 프락시를 설정합니다(예시: get 트랩은 target의 프로퍼티를 읽을 때, set 트랩은 target의 프로퍼티를 쓸 때 활성화됨).
  • proxy에 작업이 가해지고, handler에 작업과 상응하는 트랩이 있으면 트랩이 실행되어 프락시가 이 작업을 처리할 기회를 얻게 됨
    • 트랩이 없으면 target에 작업이 직접 수행됨

⇒ 트랩이 없는 프락시를 사용한 예시

let target = {};
let proxy = new Proxy(target, {}); // 빈 핸들러

proxy.test = 5; // 프락시에 값을 씁니다. -- (1)
alert(target.test); // 5, target에 새로운 프로퍼티가 생겼네요!

alert(proxy.test); // 5, 프락시를 사용해 값을 읽을 수도 있습니다. -- (2)

for(let key in proxy) alert(key); // test, 반복도 잘 동작합니다. -- (3)

위 예시의 프락시엔 트랩이 없기 때문에 proxy에 가해지는 모든 작업은 target에 전달됨

  1. proxy.test=를 이용해 값을 쓰면 target에 새로운 값이 설정됨
  2. proxy.test를 이용해 값을 읽으면 target에서 값을 읽어옴
  3. proxy를 대상으로 반복 작업을 하면 target에 저장된 값이 반환됨
  • 트랩이 없으면 proxy는 target을 둘러싸는 투명한 래퍼가 됨

Untitled

  • Proxy는 일반 객체와는 다른 행동 양상을 보이는 '특수 객체(exotic object)'임
    • 프로퍼티가 없음 -  handler가 비어있으면 Proxy에 가해지는 작업은 target에 곧바로 전달됨

⇒ 트랩을 사용해 가로챌 수 있는 작업

  • 모든 내부 메서드엔 대응하는 트랩이 존재

Untitled


학습 코드

⇒ get 트랩으로 프로퍼티 기본값 설정하기

프로퍼티 읽기를 가로채려면 handler에 get(target, property, receiver) 메서드가 있어야 합니다.

get메서드는 프로퍼티를 읽으려고 할 때 작동합니다. 인수는 다음과 같습니다.

  1. target – 동작을 전달할 객체로 new Proxy의 첫 번째 인자입니다.
  2. property – 프로퍼티 이름
  3. receiver – 타깃 프로퍼티가 getter라면 receiver는 getter가 호출될 때 this 입니다.
    대개는 proxy 객체 자신이 this가 됩니다. 프락시 객체를 상속받은 객체가 있다면 해당 객체가 this가 되기도 하죠.
let numbers = [1, 3, 5];

numbers = new Proxy(numbers, {
  get(target, prop) {
    if (prop in target) {
      return target[prop];
    } else {
      return 0; // 기본값
    }
  }
});

console.log(numbers)            // [ 1, 3, 5 ]
console.log( numbers[1] );      // 3
console.log( numbers[123] );    // 0 (해당하는 요소가 배열에 없으므로 0이 반환됨)

// 변수에 키가 있는 것은 값을 반환, 없는 것은 키를 반환
let words = {
    'company' : 'ABC',
    'name' : 'PCY',
    'age' : 25
};

words = new Proxy(words, {
    get(target, prop){
        if (prop in target) return target[prop];
        
        else return prop;
    }
});

console.log(words);             // { company: 'ABC', name: 'PCY', age: 25 }
console.log(words['name']);     // PCY  >>> proxy에 있으므로 값을 반환
console.log(words['live']);     // live >>> proxy에 없으므로 키워드를 반환

⇒ set 트랩으로 프로퍼티 값 검증하기

set(target, property, value, receiver):

  1. target – 동작을 전달할 객체로 new Proxy의 첫 번째 인자입니다.
  2. property – 프로퍼티 이름
  3. value – 프로퍼티 값
  4. receiver – get 트랩과 유사하게 동작하는 객체로, setter 프로퍼티에만 관여합니다.

구현해야 할 set 트랩은 숫자형 값을 설정하려 할 때만 true를,
그렇지 않은 경우엔(TypeError가 트리거되고) false를 반환

set 트랩을 사용해 배열에 추가하려는 값이 숫자형인지 검증

let nums  = [];

nums = new Proxy(nums, {
    set(target, prop, val){ // 프로퍼티에 값을 쓰는 동작을 가로챕니다.
        if (typeof val == 'number'){ 
            target[prop] = val;
            return true;    // 반드시 set트랩 사용시 true를 반환해야 한다!
        }
        else {
            return false;
        }
    }
});

nums.push(3);
nums.push(-5);
nums.push(1);
nums.push(7);
nums.push(-8);
console.log("Length is: " + nums.length); // Length is: 5
nums.push('hello'); // TypeError: 'set' on proxy: trap returned falsish for property '5'

// nums와 numsMig는 서로 push한 값을 공유함...
let numsMig = nums;
console.log(numsMig);

nums.push(100);
console.log("nums = " + nums);
console.log("numsMig = " + numsMig);

numsMig.push(200);
console.log("nums = " + nums);
console.log("numsMig = " + numsMig);

과제

⇒ 1번

/* 과제 1번 :  존재하지 않는 프로퍼티를 읽으려고 할 때 에러 던지기
존재하지 않는 프로퍼티 값을 읽으려고 하면 보통은 undefined가 반환됩니다.
undefined 대신 에러를 던지는 프락시를 만들어봅시다.
이렇게 해 놓으면 프로그래밍 중에 저지르는 실수를 미연에 방지할 수 있을 겁니다.
객체 target을 받는 함수 wrap(target)를 만들고 위에서 언급한 기능을 구현하여 
함수 wrap(target)이 프락시를 반환하도록 해보세요.
함수는 아래와 같이 동작해야 합니다.

let user = {
    name: "John"
};
  
function wrap(target) {
    return new Proxy(target, {
        // 여기에 코드를 작성하세요. 
    });
}
  
user = wrap(user);

console.log(user.name); // John
console.log(user.age); // ReferenceError: Property doesn't exist "age"
*/

/* 내 해답 >>> 하드 코딩적인 느낌 */
let user = {
    name: "John"
};
  
function wrap(target) {
    return new Proxy(target, {
        get(tar, prop){
            if (prop in tar){
                return tar[prop];
            }
            else return `ReferenceError: Property doesn't exist "${prop}"`
        }
    });
}
  
user = wrap(user);

console.log(user.name); // John
console.log(user.check); // ReferenceError: Property doesn't exist "check"

/* 정답 > throw 활용 */
let answer = {
    name: "PCY"
};

function wrap(target) {
    return new Proxy(target, {
        get(target, prop, receiver) {
        if (prop in target) {
            return Reflect.get(target, prop, receiver);
        } else {
            throw new ReferenceError(`Property doesn't exist: "${prop}"`);
        }
        }
    });
}

answer = wrap(answer);

console.log(answer.name); // John
console.log(answer.age); // ReferenceError: Property doesn't exist "age"

⇒ 2번

/* 음수 인덱스를 사용해 배열 요소에 접근하기

몇몇 프로그래밍 언어는 음수 인덱스를 사용해 배열 끝을 기준으로 요소에 접근할 수 있게 해줍니다.

아래와 같이 말이죠.

let array = [1, 2, 3];
array[-1]; // 3, 마지막 요소
array[-2]; // 2, 뒤에서 두 번째 요소
array[-3]; // 1, 뒤에서 세 번째 요소

위 예시에서 array[-N]는 array[array.length - N]와 동일합니다.
이렇게 음수 인덱스를 사용해 배열 요소에 접근할 수 있도록 해주는 프락시를 만들어봅시다.

최종 결과는 아래 조건을 만족해야 합니다.

let array = [1, 2, 3];

array = new Proxy(array, {
  // 여기에 코드를 작성하세요.
});

alert( array[-1] ); // 3
alert( array[-2] ); // 2

// 배열 기능은 "변함없이 그대로" 동작해야 합니다.
*/

let array = [1, 2, 3];

array = new Proxy(array, {
  get(target, prop, receiver) {
    if (prop < 0) {
      // arr[1] 같은 형태로 배열 요소에 접근하는 경우에도
      // prop은 문자열이기 때문에 숫자로 바꿔줘야 합니다.
      prop = +prop + target.length;
    }
    return Reflect.get(target, prop, receiver);
  }
});

console.log(array[-1]); // 3
console.log(array[-2]); // 2

언제 또는 무엇을 위해 Proxy를 사용하는 게 좋을까?

⇒ Chat GPT 답변

  • 유효성 검사: 프록시는 개체에 속성 값을 설정할 때 입력의 유효성을 검사할 수 있습니다. 예를 들어 속성에 할당된 값이 실제로 설정하기 전에 특정 기준(예: 특정 유형 또는 범위 내)을 충족하는지 확인할 수 있습니다.
  • 로깅 및 디버깅: 프록시를 사용하면 디버깅에 도움이 되는 속성 또는 메서드 호출에 대한 액세스를 기록할 수 있습니다. 이를 통해 개발자는 애플리케이션의 특정 부분이 언제 어떻게 액세스되거나 수정되는지 이해할 수 있습니다.
  • 성능 모니터링: 메서드 호출을 가로채면 프록시를 사용하여 성능을 모니터링할 수 있으므로 작업을 실행하는 데 걸리는 시간을 측정할 수 있습니다.
  • 액세스 제어: 프록시는 개체의 특정 속성에 대한 액세스를 제어할 수 있습니다. 이는 개체의 읽기 전용 보기를 구현하거나 사용자에게 특정 속성을 숨기는 데 사용할 수 있습니다.
  • 데이터 바인딩 및 관찰 가능성: 프록시는 일부 프레임워크에서 개체 및 배열의 변경 사항을 감지하여 반응형 프로그래밍 패턴을 활성화하는 데 사용됩니다. 속성이 변경되면 프록시는 관찰자에게 알리거나 UI 업데이트를 렌더링할 수 있으므로 반응형 프레임워크의 기본 기술이 됩니다.
  • 메모이제이션: 프록시를 사용하여 입력 인수를 기반으로 함수 호출 결과를 캐시할 수 있습니다. 이는 동일한 인수를 사용하여 반복적으로 호출되는 비용이 많이 드는 작업에 특히 유용합니다.
  • API 가로채기 및 수정: 프록시를 사용하면 개체와의 상호 작용을 가로채고 수정할 수 있으며 이는 타사 API 작업에 유용할 수 있습니다. 예를 들어, API 호출을 가로채서 요청이나 응답을 즉시 수정할 수 있습니다.
  • 가상화: 프록시는 개체에 실제로 존재하지 않는 특정 속성이나 메서드가 있는 것처럼 보이는 가상 개체를 생성할 수 있습니다. 이는 가상 DOM과 같은 패턴을 구현하는 데 유용할 수 있습니다.

요약하면 프록시는 객체 작업 동작을 가로채고 사용자 정의하기 위한 강력하고 유연한 메커니즘을 제공합니다. 보안 및 검증 강화부터 애플리케이션의 반응성 및 관찰 가능성과 같은 고급 프로그래밍 패턴 활성화에 이르기까지 다양한 용도로 사용됩니다.

느낀점

상당히 유용한 기능이라고 생각한다. Chat GPT를 통해서 언제 그리고 무엇을 위해 Proxy를 사용하는 것이 좋냐고 물어봤을 때 여러가지 답변이 나왔는데, 그중에서도 API 가로채기 및 수정과 가상화에 좋다고 나왔다.

분명, 실무에서도 자주 사용될 것이라고 생각한다. 내 개인 프로젝트에서도 사용해보면 좋을 것 같다.

profile
#Software Engineer #IRISH

0개의 댓글