Javascript | Proxy 와 Reflect 알아보기

이동욱·2024년 1월 10일
0
post-thumbnail

Proxy 객체는?

Proxy 객체는 다른 객체를 감싸고 해당 객체에 대한 특정 동작을 가로채거나 수정하는 데 사용할 수 있다.
객체의 속성 검증, 가상 속성 생성, 캐싱 등 다양한 목적으로 사용한다.

Proxy 생성

let proxy = new Proxy(감시_객체, 핸들러)

첫번째 인자로 Proxy로 감쌀 객체가 들어가고
두번째 인자로는 특정 동작을 가로채는 메서드들이 담겨있는 핸들러 객체가 들어간다.

💡 Proxy 핸들러 리스트

  • property
    • get
    • set
    • has
    • deleteProperty
  • method
    • apply
    • constructor
  • Object
    • getPrototypeOf
    • setPrototypeOf
    • isExtensible
    • preventExtensions
    • getOwnPropertyDescriptor
    • ownKeys

proxy에 작업이 생겼을때 핸들러에 해당 작업에 대한 메서드가 정의되어 있으면 proxy에 작업이 처리된다.

핸들러가 비어있다면 proxy에 가해지는 작업은 감시_객체에 곧바로 전달 된다.

const ageList = {};

let proxyAgeList = new Proxy(ageList, {});

proxyAgeList.ddowoo = "10";
	
console.log(target); // { ddowoo: '10' }
console.log(proxy); // { ddowoo: '10' }



Property 활용

1. get(target, property)

  • 객체에 프로퍼티값을 가져올 때 호출된다.

target : 동작을 전달할 객체
property : 프로퍼티 키

해당 프로퍼티가 존재하면 해당 값을 반환하고, 존재하지 않는 경우에는 커스텀한 값을 반환하거나 다른 동작을 수행할 수 있다.

const ageList = {};

let proxyAgeList = new Proxy(ageList, {
  get: (target, prop) => {
    if (prop in target) {
      return target[prop];
    } else {
      return "존재하지 않는 유저입니다.";
    }
  },
});

console.log(ageList.ddowoo); // undefined
console.log(proxyAgeList.ddowoo); // 존재하지 않는 유저입니다.

2. set(target, property, value, receiver)

  • 객체에 프로퍼티를 지정할 때 호출된다.
  • 허용되는 경우 true를 반환하고, 그렇지 않은 경우 false를 반환한다.

target : 동작을 전달할 객체
property : 프로퍼티 키
value : 프로퍼티 값
receiver : 할당이 지시된 원래 객체

const ageList = {};

let proxyAgeList = new Proxy(ageList, {
  set: (obj, prop, value) => {
    console.log("obj :", obj);
    console.log("prop : ", prop);
    console.log("value : ", value);

		// 숫자인 경우에만 할당 
    if (typeof value === "number") {
      obj[prop] = value;
      return true;
    } else {
      return false;
    }
  },
});

proxyAgeList.ddowoo = '5'; // 숫자가 아닌경우
// obj : {}
// prop :  ddowoo
// value :  '5'
console.log(ageList.ddowoo); // undefined
console.log(proxyAgeList.ddowoo); // undefined

proxyAgeList.ddowoo = 5; // 숫자인 경우
// obj : {}
// prop :  ddowoo
// value :  5
console.log(ageList.ddowoo); // 5
console.log(proxyAgeList.ddowoo); // 5

💡 true를 반환하지 않으면 TypeError이 발생할수도 있다 한다. 꼭 true를 반환해주자

3. deleteProperty(target, property)

  • delete 연산자를 사용하여 프로퍼티를 삭제할 때 호출된다
  • 삭제가 허용되는 경우 true를 반환하고, 그렇지 않은 경우 false를 반환한다.

target : 동작을 전달할 객체
property : 프로퍼티 키

const ageList = {
  ddowoo: 10,
  dong: 10,
};

let proxyAgeList = new Proxy(ageList, {
  deleteProperty: (obj, prop) => {
    if (prop === "ddowoo") {
      return false;
    } else {
      delete obj[prop];
      return true;
    }
  },
});

delete proxyAgeList.ddowoo;
delete proxyAgeList.dong;

console.log(proxyAgeList.ddowoo); // 10 (안지워짐)
console.log(proxyAgeList.dong); // undefined

4. has(target, property)

  • in 연사자가 사용될 때 호출된다.

target : 동작을 전달할 객체
property : 프로퍼티 키

has 핸들러를 사용해 _로 시작하는 키를 가진 프로퍼티가 있는 경우, in 연산자를 통해 해당 키를 조회되지 않도록 작업했습니다.

// has

const ageList = {
  _ddowoo: 10,
  ddowoo: 10,
};

let proxyAgeList = new Proxy(ageList, {
  has: (obj, prop) => {
    if (prop.startsWith("_")) {
      return false;
    } else {
      return obj.hasOwnProperty(prop);
    }
  },
});

console.log("_ddowoo" in proxyAgeList); // false
console.log("ddowoo" in proxyAgeList); // true

5. apply(target, thisArg,argumentsList)

  • 함수를 Proxy로 감싸 함수을 실행시킬 때 사용될 때 호출된다.

target : 원본 함수
thisArg : 함수가 호출될 때 this 값
argumentsList : 호출에 대한 인수 목록

function tellAge(age) {
  return `나이는 ${age}살 입니다.`;
}

const tellAgeProxy = new Proxy(tellAge, {
  apply: function (target, thisArg, argumentsList) {
    return `만나이는 ${argumentsList[0] - 2}살 입니다.`;
  },
});

const result1 = tellAge(10);
const result2 = tellAgeProxy(10);

console.log(result1); // 나이는 10살 입니다.
console.log(result2); // 만나이는 8살 입니다.



Reflect는?

Reflect는 Proxy와 같이 명령을 가로챌 수 있는 메서드를 제공하는 내장 객체이다.
Reflect는 생성자가 아니기 때문에 Proxy와 같이 생성자로는 사용할 수 없다. 메서드 종류는 프록시의 핸들러 메서드와 동일하다.

Reflect로 객체에 여러 동작을 수행할 때 코드상 통일된 방식으로 작성해 유지보수가 용이하고, 예외처리를 간소하게 할수 있다.

1.get

  • 객체 조회
const ageList = { ddowoo : 10 }

console.log(Reflect.get(ageList,'ddowoo')); // 10

2.set

  • 객체 값 설정
const ageList = {}

console.log(Reflect.set(ageList,'ddowoo',10)); // true
console.log(ageList.ddowoo);  // 10

3.has

  • 객체 값 존재 여부 확인
const ageList = {ddowoo : 10}

console.log(Reflect.has(ageList,'ddowoo')); // true



Proxy + Reflect

Proxy의 핸들러 함수에서 직접 target 파라미터 객체에 작업을 하는 방식은 예상치 못한 오류가 생길수 있다. 이런 경우 Reflect로 명시적 대상 객체에 동작을 수행하면 부작용을 줄일수 있어 주로 Proxy와 함께 쓰인다.

예시코드

const ageList = {};

let proxyAgeList = new Proxy(ageList, {
  get: (obj, prop) => {
  
    // if (prop in obj) { => 기존 코드
    if (Reflect.has(obj, prop)) {
      // return obj[prop]; => 기존 코드
      return Reflect.get(obj, prop);
    } else {
      return "존재하지 않는 유저입니다.";
    }
  },
  set: (obj, prop, value) => {
    if (typeof value === "number") {
    // obj[prop] = value; => 기존 코드
	// return true; => 기존 코드                      
      return Reflect.set(obj, prop, value); // 리턴값으로 true
    } else {
      return false;
    }
  },
});

proxyAgeList.ddowoo = 10;
console.log(ageList); // { ddowoo: 10 }
console.log(proxyAgeList); // { ddowoo: 10 }

console.log(ageList.ddowoo); // 10
console.log(proxyAgeList.ddowoo); // 10

console.log(ageList.John); // undefined
console.log(proxyAgeList.John); // 존재하지 않는 유저입니다.



참조
Reflect - JavaScript | MDN
Proxy와 Reflect
JS-📚-자바스크립트-Proxy-Reflect-고급-기법

profile
프론트엔드

0개의 댓글