320x100
320x100

Proxy

: 특정 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체

: 가로채진 작업은 Proxy 자체에서 처리되거나 원래 객체가 처리하도록 그대로 전달도 가능

 

let proxy = new Proxy(target, handler)

- target

: 감싸게 될 객체로, 함수를 포함한 모든 객체 가능

 

- handler

: 동작을 가로채는 메서드인 trap이 담긴 객체

: 여기서 프락시를 설정

 

=> proxy에 작업이 가해지고 handler 작업과 상응하는 트랩이 있으면

     트랩이 실행되어 프락시가 작업을 처리

     트랙이 없으면 target에 작업이 직접 수행됨

 

 

 

 

trap이 없는 프락시

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.test = 를 이용해 값을 쓰면 target에 새로운 값이 설정됨

: proxy.test를 이용해 값을 읽으면 target에서 값을 읽어옴

: proxy를 대상으로 반복 작업을 하면 target을 둘러싸는 투명한 래퍼가 됨

 

 

 

 

객체 관점의 Proxy

: Proxy는 일반 객체와는 다른 행동 양상을 보이는 특수 객체

: 프로퍼티가 없음

: handler가 비어있으면 proxy에 가해지는 작업은 target에 곧바로 전달됨

 

 

 

 

트랩을 추가하여 Proxy의 기능을 활성화 하기

: 객체가 어떤 작업을 할 대는 자바스크립트 명세서에 적의된 내부 메서드가 깊숙한 곳에서 관여함

: 프로퍼티를 읽을 때는 [[Get]]이라는 내부 메서드가, 

 프로퍼티를 쓸 때는 [[Set]]이라는 내부 메서드가 관여

: 이런 내부 메서드들은 명세서에만 정의 되었기 때문에 개발자가 호출할 수 없음

: 프락시의 트랩은 내부 메서드의 호출을 가로챔

 

 

 

 

내부 메서드에 대응하는 트랩

 

 

 

프락시의 규칙

: 값을 쓰는게 성공적으로 처리되었으면 [[Set]]은 반드시 true와 false를 반환

: 값을 지우는게 성공적으로 처리되었으면 [[Delete]]는 반드시 true 혹은 false를 반환

: 기타 등등 (refference 참조)

 

 

 

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

: 프로퍼티 읽기를 가로채기 위해서는 handler에 get (target, property, receiver) 메서드가 필요

 

- target

: 동작을 전달할 객체로 new Proxy의 첫 번째 인자

 

- property

: 프로퍼티의 이름

 

- receiver

: 타깃 프로퍼티가 getter라면 receiver는 getter가 호출될 때의 this

: 대개는 proxy 객체 자신이 this가 됨

: 프락시 객체를 상속받은 객체가 있다면 해당 객체가 this가 되기도 함

 

- get을 이용해 트랩을 만드는 예시 

let numbers = [0, 1, 2];

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

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

 

- 구절과 번역문이 저장되어 있는 dictionary에 대한 예시

let dictionary = {
  'Hello': '안녕하세요',
  'Bye': '안녕히 가세요'
};

dictionary = new Proxy(dictionary, {
  get(target, phrase) { // 프로퍼티를 읽기를 가로챕니다.
    if (phrase in target) { // 조건: 사전에 구절이 있는 경우
      return target[phrase]; // 번역문을 반환합니다
    } else {
      // 구절이 없는 경우엔 구절 그대로를 반환합니다.
      return phrase;
    }
  }
});

// 사전을 검색해봅시다!
// 사전에 없는 구절을 입력하면 입력값이 그대로 반환됩니다.
alert( dictionary['Hello'] ); // 안녕하세요
alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (입력값이 그대로 출력됨)

 

 

 

 

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

: 프로퍼티에 값을 쓰려고 할 때 이를 가로채는 set 트랩을 사용

: set (target, property, value, receiver)

 

- target

: 동작을 전달할 객체로 new Proxy의 첫 번째 인자

 

- property

: 프로퍼티의 이름

 

- value

: 프로퍼티의 값

 

- receiver

: setter 프로퍼티에만 관여하는 this 객체

let numbers = [];

numbers = new Proxy(numbers, { // (*)
  set(target, prop, val) { // 프로퍼티에 값을 쓰는 동작을 가로챕니다.
    if (typeof val == 'number') {
      target[prop] = val;
      return true;
    } else {
      return false;
    }
  }
});

numbers.push(1); // 추가가 성공했습니다.
numbers.push(2); // 추가가 성공했습니다.
alert("Length is: " + numbers.length); // 2

numbers.push("test"); // Error: 'set' on proxy

alert("윗줄에서 에러가 발생했기 때문에 이 줄은 절대 실행되지 않습니다.");

=> true를 반드시 반환해야함

 

 

 

 

ownKeys와 getOwnPropertyDescriptor로 반복 작업하기

: Object.keys, for in 반복문을 비롯한 프로퍼티 순환 관련 메서드 대다수는

  내부 메서드 [[OwnPropertyKeys]]를 사용해 프로퍼티 목록을 얻음

let user = { };

user = new Proxy(user, {
  ownKeys(target) { // 프로퍼티 리스트를 얻을 때 딱 한 번 호출됩니다.
    return ['a', 'b', 'c'];
  },

  getOwnPropertyDescriptor(target, prop) { // 모든 프로퍼티를 대상으로 호출됩니다.
    return {
      enumerable: true,
      configurable: true
      /* 이 외의 플래그도 반환할 수 있습니다. "value:..."도 가능합니다. */
    };
  }

});

alert( Object.keys(user) ); // a, b, c

 

 

 

 

deleteProperty와 여러 트랩을 사용해 프로퍼티 보호하기

- 필요한 트랩

: get = 프로퍼티를 읽으려고 하면 에러를 던져줌
: set = 프로퍼티에 쓰려고 하면 에러를 던져줌
: deleteProperty = 프로퍼티를 지우려고 하면 에러를 던져줌
: ownKeys = for..in이나 Object.keys같은 프로퍼티 순환 메서드를 사용할 때 _로 시작하는 메서드는 제외함

let user = {
  name: "John",
  _password: "***"
};

user = new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error("접근이 제한되어있습니다.");
    }
    let value = target[prop];
    return (typeof value === 'function') ? value.bind(target) : value; // (*)
  },
  set(target, prop, val) { // 프로퍼티 쓰기를 가로챕니다.
    if (prop.startsWith('_')) {
      throw new Error("접근이 제한되어있습니다.");
    } else {
      target[prop] = val;
      return true;
    }
  },
  deleteProperty(target, prop) { // 프로퍼티 삭제를 가로챕니다.
    if (prop.startsWith('_')) {
      throw new Error("접근이 제한되어있습니다.");
    } else {
      delete target[prop];
      return true;
    }
  },
  ownKeys(target) { // 프로퍼티 순회를 가로챕니다.
    return Object.keys(target).filter(key => !key.startsWith('_'));
  }
});

// "get" 트랩이 _password 읽기를 막습니다.
try {
  alert(user._password); // Error: 접근이 제한되어있습니다.
} catch(e) { alert(e.message); }

// "set" 트랩이 _password에 값을 쓰는것을 막습니다.
try {
  user._password = "test"; // Error: 접근이 제한되어있습니다.
} catch(e) { alert(e.message); }

// "deleteProperty" 트랩이 _password 삭제를 막습니다.
try {
  delete user._password; // Error: 접근이 제한되어있습니다.
} catch(e) { alert(e.message); }

// "ownKeys" 트랩이 순회 대상에서 _password를 제외시킵니다.
for(let key in user) alert(key); // name

 

 

 

 

 

has 트랩으로 범위 내 여부 확인하기 

: has(target, property)

 

- target 

: new Proxy의 첫 번째 인자로 전달되는 타깃 객체

 

- property

: 프로퍼티 이름

let range = {
  start: 1,
  end: 10
};

range = new Proxy(range, {
  has(target, prop) {
    return prop >= target.start && prop <= target.end;
  }
});

alert(5 in range); // true
alert(50 in range); // false

 

 

 

 

 

apply 트랩으로 함수 감싸기

: apply(target, thisArg, args)

: 프락시를 함수처럼 호출하려고 할 때 동작

- target

: 타깃 객체(자바스크립트에서 함수는 객체임)

 

- thisArg 

: this의 값

 

- args 

: 인수 목록

function delay(f, ms) {
  return new Proxy(f, {
    apply(target, thisArg, args) {
      setTimeout(() => target.apply(thisArg, args), ms);
    }
  });
}

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

sayHi = delay(sayHi, 3000);

alert(sayHi.length); // 1 (*) 프락시는 "get length" 연산까지 타깃 객체에 전달해줍니다.

sayHi("John"); // Hello, John! (3초 후)

: proxy 객체는 타깃 객체의 모든 것을 전달해 주므로 훨씬 강력함

 

 

 

 

 

 

Reflect

: Refference 참조

 

 

 

 

 

Refference

 

Proxy와 Reflect

 

ko.javascript.info

 

300x250
728x90