LHJ

I'm a FE developer.

21.4 프락시

05 Jun 2020 » js_lj

21.4 프락시

프락시(proxies)는 ES6에서 새로 추가된 메타프로그래밍 기능입니다.
메타프로그래밍이란 프로그램이 자기 자신을 수정하는 것을 말합니다.

객체 프락시는 간단히 말해 객체에 대한 작업을 가로채고, 필요하다면 작업 자체를 수정하는 기능입니다.
프로퍼티 접근을 수정하는 예제를 만들어 봅시다.
먼저 프로퍼티 몇 개가 있는 일반적인 객체입니다.

const coefficients = {
    a: 1,
    b: 2,
    c: 5,
}

이 객체의 프로퍼티가 수학의 계수(coefficient)라고 생각해 봅시다.
다음과 같은 식으로 사용할 수 있을 겁니다.

function evaluate(x, co) {
    return co.a + co.b * x + co.c * Math.pow(x, 2);
}

지금까지는 좋습니다.
2차 방정식의 계수를 객체에 저장하고, x 값이 무엇이든 방정식을 계산할 수 있습니다.
그런데 계수 일부가 빠진 객체를 가지고 계산하려 하면 어떻게 될까요?

const coefficients = {
    a: 1,
    c: 5,
}
evaluate(5, coefficients);      // NaN

coefficients.b에 0을 할당하면 문제를 해결할 수 있지만, 프락시를 쓰는 방법이 더 낫습니다.
프락시는 객체를 대상으로 한 작업을 가로채므로, 정의되지 않은 프로퍼티는 항상 0을 반환하게 만들 수 있습니다.
coefficients 객체에 프락시를 만듭시다.

function evaluate(x, co) {
    return co.a + co.b * x + co.c * Math.pow(x, 2);
}

const coefficients = {
    a: 1,
    c: 5,
}
const betterCoefficients = new Proxy(coefficients, {
    get(target, key) {
        return target[key] || 0;
    },
})
evaluate(5, betterCoefficients)

NOTE_
이 책을 쓰는 시점에서 바벨은 프락시를 지원하지 않고 있습니다.
하지만 파이어폭스 최신 버전은 프락시를 지원하므로 관련된 코드를 파이어폭스에서 테스트할 수 있습니다.

역주_
역자가 사용중인 노드 버전은 6.10.0이며 별다른 작업 없이 프락시를 정상적으로 지원하고 있습니다.

Proxy 생성자에 넘기는 첫 번째 매개변수는 타겟, 즉 프락시할 객체입니다.
두 번째 매개변수는 가로챌 동작을 가리키는 핸들러입니다(프로퍼티 접근자인 get와는 다릅니다. 이 핸들러는 일반적인 프로퍼티나 접근자 프로퍼티 모두 동작합니다).
get 함수는 매개변수로 타겟, 프로퍼티 키(문자열 또는 심볼), 수신자(프락시 자체 또는 프락시에서 파생되는 것)를 받습니다.
이 예제에서는 타겟과 프로퍼티 키만 사용했습니다.

이 예제에서는 해당 키가 타겟에 있는지 확인하고, 없으면 0을 반환합니다.
직접 테스트해 보십시오.

const coefficients = {
    a: 1,
    c: 5,
}
const betterCoefficients = new Proxy(coefficients, {
    get(target, key) {
        return target[key] || 0;
    },
})

betterCoefficients.a;               // 1
betterCoefficients.b;               // 0
betterCoefficients.c;               // 5
betterCoefficients.d;               // 0
betterCoefficients.anything;        // 0

coefficients 객체의 프락시에는 무한한 프로퍼티가 있고, 직접 정의한 프로퍼티를 제외하면 모두 값이 0인 것이나 마찬가지입니다.

키로 소문자 한 글자를 받았을 때만 프락시가 동작하게 할 수도 있습니다.

const coefficients = {
    a: 1,
    c: 5,
}
const betterCoefficients = new Proxy(coefficients, {
    get(target, key) {
        if (!/^[a-z]$/.test(key)) return target[key];
        return target[key] || 0;
    },
})

target[key]가 참 같은 값인지만 체크하지 않고, 키의 값이 숫자가 아닐 때는 항상 0을 반환하게 할 수도 있지만 그건 독자의 연습문제로 남겨두겠습니다.

마찬가지로 프로퍼티에 값을 할당하려 할 때 set 핸들러로 가로챌 수 있습니다.
객체에 위험한 프로퍼티가 있어서 한 단계를 더 거치지 않으면 값을 할당하거나 메서드를 호출할 수 없게 하려고 합니다.
거쳐야 할 단계는 allowDangerousOperations setter입니다.
이 값이 true일 때만 위험한 프로퍼티에 접근할 수 있습니다.

const cook = {
    name: "Walt",
    redPhosphorus: 100,     // 위험합니다.
    water: 500,             // 안전합니다.
};
const protectedCook = new Proxy(cook, {
    set(target, key, value) {
        if (key === 'redPhosphorus') {
            if (target.allowDangerousOperations)
                return target.redPhosphorus = value;
            else
                return console.log("Too dangerous!");
        }
        // 다른 프로퍼티는 모두 안전합니다.
        target[key] = value;
    },
})

protectedCook.water = 550;              // 550
protectedCook.redPhosphorus = 150;      // Too dangerous!

protectedCook.allowDangerousOperations = true;
protectedCook.redPhosphorus = 150;      // 150

이 장에서 설명한 것은 프락시로 할 수 있는 일의 극히 일부에 불과합니다.
프락시에 대해 더 알고 싶다면 악셀 라우슈마이어(Axel Rauschmayer)가 쓴 글인 ‘ES6 프락시와 메타프로그래밍’을 먼저 읽고, MDN 문서 http://mzl.la/1QZKM7U를 읽길 권합니다.