A banner created from here

ES6 에서 새롭게 추가된 Proxy 객체에 대해 소개합니다

Contents


  1. Proxy 객체란
  2. Proxy Pattern 과 관련이 있을까?
  3. Proxy 의 동작
  4. Proxy Examples
  5. 마치며

Proxy 객체란


ES6 에서 추가된 Proxy 객체는 기본적인 동작의 새로운 행동을 정의할 때 사용합니다
기본적인 동작이란 아래의 동작들 (+@) 이 되겠습니다

  1. property 접근
  2. property 값 할당
  3. 순회 / 열거 (deprecated 되었습니다. 참고)
  4. 함수 호출
  5. 생성자 호출
    ...

코드로는 이런 동작들이 되겠네요

const obj = { a: 1 }
obj.a // property 접근
obj.a = 3 // property 값 할당

메서드 오버라이드와 비슷하다는 생각도 들었습니다
상속한 클래스가 어떤 메서드의 동작을 변경하는 것이
대리 객체가 기존 객체를 받아서 동작을 추가하는 것과 비슷하다고 생각했거든요

Method Override vs Proxy Pattern 에 관해서는 이 질문을 참고해주세요

Proxy Pattern 과 관련이 있을까?


사실 자바스크립트에서 원시값을 제외한 모든 것이 Object 이기 때문에
디자인 패턴으로써의 Proxy Pattern 을 좀 더 쉽게 사용할 수 있게 도와주는 객체라 생각합니다

Proxy Pattern 을 사용해야 하는 부분은 Proxy 객체로도 대부분 해결이 가능합니다
그러니까, Proxy 객체는 Proxy Pattern 을 위한 구현체라 생각합니다

Proxy Pattern 은 기본적으로 아래의 조건을 만족 합니다

image.png
Proxy Pattern UML

  1. 동일한 인터페이스를 구현(Implements)합니다
  2. Proxy 객체는 본래의 객체 대신 대입되어도 항상 같은 동작이 보장 되어야 합니다
  3. 본래의 객체에 대한 접근을 제어 합니다

가상 프록시, 원격 프록시, 보호 프록시 등 다양한 방법으로 활용 됩니다

Proxy 의 동작


Proxy 는 기본적인 동작의 새로운 행동을 정의하기 위한 traps 를 제공합니다
traps 는 운영체제의 그것과 흡사하다는 MDN 문서의 설명이 있습니다

그래서 운영체제에서의 trap 에 대해 찾아보았습니다

컴퓨터 과학에서 트랩(Trap)은 어떤 프로세스가 특정 시스템 기능을 사용하려고 할 때
그 기능을 운영체제에게 요청하는 방법을 말한다.
... Wikipedia

아마 특정 시스템 기능이라는 것이 기본적인 동작과 대응되는 개념이라는 생각이 듭니다
객체 속성에 접근하고, 값을 부여하고, 함수를 호출하는 기본적인 동작에 대한 요청을
trap 이라는 것을 사용하여 요청한다는 의미로 이해 하였습니다

traps 목록(Proxy.prototype)은 2019년 3월 현재 기준으로 아래와 같습니다

  1. .isExtensible()
  2. .preventExtensions()
  3. .getOwnPropertyDescriptor()
  4. .defineProperty()
  5. .has()
  6. .get()
  7. .set()
  8. .deleteProperty()
  9. .ownKeys()
  10. .apply()
  11. .construct()

배열의 특정 인덱스에 접근하는 것도 [[Get]] 이라는
객체의 내장 프로퍼티를 활용한다는 점을 이미 알고 계실 것입니다
(궁금하시면 이 곳 을 참고해주세요)

왜냐면, 자바스크립트의 배열 또한 Object 이기 때문이죠!
그렇기때문에 저 [[Get]] 의 동작을 가로채는 trap 을 활용하면
배열에서 값을 가져올 때, 동작을 추가적으로 구현할 수 있을것 같습니다

배열의 값에 접근할 때, 그 객체의 이름 을 출력하는 Proxy 객체를 만들어 봅니다

const cats = [
  {name: 'DD', age: 4, color: 'coffee'},
  {name: 'TT', age: 4, color: 'gray'},
];

const catsProxy = new Proxy(cats, {
  // [[Get]] 의 동작에 추가 기능을 구현 합니다
  get: function(target, property) {
    const ret = target[property];
    const { name } = ret;
    console.log(`Come here ${name}!`)
    return ret;
  }
});

catsProxy[0] // Come here DD!

catsProxy 배열의 0번째 인덱스에 접근할 때, 객체의 이름을 가져와 출력했습니다.
이렇게 기본적인 동작 에 기능을 추가하는 것이 가능해 집니다

Proxy Examples


데이터의 Two-way Binding 에 활용

먼저 Two-way binding 이란 이런 개념입니다

Two-way binding just means that:

  1. When properties in the model get updated, so does the UI.
    → 모델(객체)의 속성 값이 업데이트 되면, UI 도 업데이트 되어야 한다
  2. When UI elements get updated, the changes get propagated back to the model.
    → UI 가 업데이트 되면, 모델에 그 값을 역전파(다시 전달) 해야 한다

Vue.js 공식 가이드 문서 중 폼 입력 바인딩 이 Two-way binding 의 좋은 예제입니다

그럼 Proxy 객체를 사용하여 이를 비슷하게 구현 해보도록 해요
(코드가 너무 길어져서 Codepen 으로 대체합니다!)

마치며


Proxy 객체에 대해 공부하게 된 이유는 console.log 의 동작을 가로채고 싶어서
무슨 방법이 있을까 고민하다가 Proxy 객체를 활용 해보면 어떨까 부터 시작했습니다

console.log, console.info 등의 메서드로 값을 출력할 때
그 값을 어딘가에 저장 해두고 싶었습니다.

실제 적용은 아래에서 확인할 수 있습니다

  • 코드: 16번째 라인을 참고 해주세요
  • 동작: 콘솔창을 열고, Clear Console 버튼 후 Restore Console 버튼을 누르면 지워졌던 내용이 다시 출력됩니다

Proxy Pattern 은 예외 처리나 Validation, 값의 캐싱 등 다양한 용도로 활용할 수 있습니다