[JavaScript] Proxy, Trap

Narcoker·2023년 6월 15일
0

JavaScript

목록 보기
41/55

Proxy

기본적으로 객체의 동작을 사용자 정의하는 방법을 제공한다.
이는 객체에 대한 작업을 가로채고, 이를 변경하거나, 새로운 동작을 추가하거나,
기본 동작을 수행하기 전에 작업을 수행할 수 있도록 한다.

new 연산자를 사용하지 않으면 TypeError 발생
handler를 작성하지 않으면 에러가 발생
Proxy로 만든 오브젝트는 constructor 가 없다.

Proxy 객체를 만들려면 두가지 인수가 필요하다
target 객체와 handler 객체이다.

let target = {};
let handler = {
  get: function(target, prop, receiver) {
    console.log(`Getting ${prop}`);
    return Reflect.get(...arguments);
  },
  set: function(target, prop, value, receiver) {
    console.log(`Setting ${prop} to ${value}`);
    return Reflect.set(...arguments);
  }
};

let proxy = new Proxy(target, handler);

proxy.a = 'example'; // Setting a to example
console.log(proxy.a); // Getting a, example

Proxy.revocable()

Proxy를 사용할 수 없는 형태로 변경할 수 있는 Proxy 를 만드는 메서드
인스턴스.revoke() 를 하면 사용불가능 해진다.

target의 프로퍼티에 접근하려면 Proxy 인스턴스.proxy.프로퍼티식별자 로 접근해야한다.

const target = {point : 100};
const handler = {
	get(target, key){
    	return target[key];
    }
};

const obj = Proxy.revocable(target, handler);
console.log(obj.proxy.point); // 100, handlr.get 실행

obj.revoke();
try {
	obj.proxy.point;
} catch {
	console.log("Proxy 기능 사용 불가"); // 실행
}    

target

Proxy 가 가로챌 객체

handler

target에 어떤 수행할 때 가로채서
어떤 수행을 할지 지정하도록 만든 메뉴얼들은 모아둔 객체

trap

handler 가 메뉴얼들을 모아둔 객체라고 했는데
trap은 메뉴얼이라고 보면 된다.

즉, target에 어떤 수행할 때 가로채서
어떤 수행을 할지 지정하도록 만든 메뉴얼이다.

모든 메서드를 Proxy 할 수 있는 것은 아니다.
위 사진은 Proxy 할 수 있는 메서드 들의 집합이다.

set() trap

target 파라미터의 값을 할당할때 가로채는 trap 이다.
파라미터

  • target : 대상 객체로 프로퍼티 값을 가져오려는 원본 객체
  • key : 가져오려는 프로퍼티 이름
  • value : key에 할당할 값
  • receiver : 프록시 인스턴스 아래 코드에서는 obj
    파라미터 명은 자유롭게 작성할 수 있다.

set trap 내부에서 this 바인딩은 handler 이다.

const target = {};
const handler = {
	set(target, key ,value, recevier){
    	target[key] = value + 200;
    }
};
const obj = new Proxy(target, handler);
obj.point = 100;
console.log(obj.point); // 300
const target = {};
const handler = {
	set(target, key ,value, recevier){
    	target[key] = value + 200;
        target.title = recevier.title +".JS";
        return true;
    }
};
const proxy = new Proxy(target, handler);

// 만든 프록시를 부모로 사용
const obj = Object.create(proxy, { title : {value: "책", writable: true} });

obj.point = 100;
console.log(obj.title); // 책
console.log(target.title); // 책.JS
const target = {point: 100};
const handler = {
	point: 123,
    set(target, key ,value, receiver) {
    	console.log(this.point); // 123
        this.book = "책";
    }
};
const obj = new Proxy(target, handler);
obj.point = 500;
console.log(handler.book); // 책
console.log(target.book); // undefined

만약 값을 할당하고자 하는 target의 프로퍼티의
프로퍼티 디스크립터 중 [[Writeable]] 이나 [[Configurable]]false
set() trap 을 사용해도 값을 변경할 수 없다.

get() trap

target 파라미터의 값을 얻고자 할때 가로채는 trap 이다.

매개변수

  • target : 대상 객체로 프로퍼티 값을 가져오려는 원본 객체
  • key : 가져오려는 프로퍼티 이름
  • receiver : 프록시 인스턴스
const target = {point : 100};
const handler = {
	get(target, key, receiver) {
    	return target[key] + 200;
    }
};
const obj = new Proxy(target, handler);
console.log(obj.point); // 300
console.log(obj.bonus); // NaN, target[bonus] = undefined

조건을 부여하여 값을 구할때 호출하는 곳마다 조건 코드를 작성하지 않고
get() trap에 조건 조드를 작성하면 클린 코드가 될 수 있다.

const target = { point: 100 };
const handler = {
	get(target, key, receiver){
    	const value = target[key];
        return this.check ? value + 200 : value;
    }
};
const obj = new Proxy(target, handler);
handler.check = true;
consoel.log(obj.point); // 300

주의할 점

프록시 설정 이후 target을 새로운 객체를 생성하여 할당할 경우
프록시 인스턴스의 [[target]] 과 target의 주소값이 달라지게 되므로
동기화가 끊긴다.

따라서 프록시 설정 이후 target을 새로운 객체로 만들지말고
수정이 필요한 경우 프로퍼티를 건드리자

만약 target 프로퍼티의 프로퍼티 디스크립터 중
[[Writable]] 이나 [[Configurable]]
false면 반환 값을 변경하여 return이 불가능하다.

get() 트랩에서 try-catch 문을 사용할 수 없다.

has() trap

in 연산자가 사용될 때 가로챈다.
in 연잔자는 객체가 특정 속성을 소유하고 있는지 확인하는 연산자이다.
이때 객체 사진의 속성 뿐아니라 상속받은 프로토타입 체인 상의 모든 속성들을 확인한다.

let target = { point : 100 };

let handler = {
    has(target, key) {
       return target[key] 
    }
};

let obj = new Proxy(target, handler);
console.log("point" in obj); // true
console.log("book" in obj); // false

만약 obj에 같은 이름의 프로퍼티 이름이 존재한다면 trap을 호출하지 않는다.

let target = { point : 100 };

let handler = {
    has(target, key) {
       return target[key] 
    }
};

const proxy = new Proxy(target, handler)
let obj = Object.create(proxy, { point : {value: 500} } );
console.log("point" in obj); // true, trap 호출 안함
console.log("bonus" in obj); // bonus

만약 오브젝트가 Object.preventExtensions() 등으로 확장 금지이거나
프로퍼티의 프로퍼티 어트리뷰트 중 [[Configurable]]false
반환 값을 변경하여 반환할 수 없다. false로 지정하면 에러가 발생한다.
true만 가능하다.

const target = {point: 100};
Object.preventExtensions(target);
const handler = {
	has(target, key) {
    	console.log("has 트랩 실행");
        //  return false; 에러 발생
        return target[key];
    }
};
const obj = new Proxy(target, handler);
console.log("point" in obj);

deleteProperty() trap

delete 연산을 호출했을때 가로채는 trap 이다.

delete 연산은 프로퍼티가 없어도 true를 반환한다.
이 트랩을 사용하여 조건을 체크하여 true/false를 반환하면 완전하게 처리할 수 있다.

const target = {point : 100};
console.log(delete target.bonus); // true, bonus 프로퍼티는 존재하지 않는다.

const handler = {
	deleteProperty(target, key){
    	if (key in target) {
        	delete target[key];
            return true;
        }
        return false;
    }
};
const obj = new Proxy(target, handler);

console.log(delete obj.point); // true
console.log(target.point); // undefined
console.log(delete obj.point); // false
console.log(delete target.point); // true, target은 원본 객체이므로 [[Delete]] 가 호출된다. 일반적인 delete 처리 결과이다.

target 프로퍼티의 프로퍼티 디스크립터 중 [[Configurable]] 이 false 이면
삭제할 수 없으므로 에러가 발생한다.

defineProperty() trap

Object.definepProperty() 의 트랩이다.

파라미터

  • target : 원본 객체
  • key : target의 프로퍼티 식별자 이름
  • desc : key의 프로퍼티 디스크립터
const target = {};
const handler = {
	defineProperty(target, key, desc){
    	if(desc.value > 0) {
        	desc,value= desc.value * -1;
        };
        Object.defineProperty(target, key, desc);
        return true;
    }
};
const proxy = new Proxy(target, handler);
Object.defineProperty(proxy, "point", {value: 100, writable: true} );
console.log(target.point); // -100

target 오브젝트가 확장 불가 상태이면 프로퍼티를 추가할 수 없다.

preventExtensions() trap

Object.preventExtension() 트랩
target 오브젝트에 오브젝트의 확장 금지를 설정한다.

const target = {point: 100};
const handler = {
	preventExtensions(target){
    	if(target.point) { // target에 point 프로퍼티가 있을 때만 확장 금지
        	Object.preventExtensions(target);
            return true;
        };
        return false;
    }
};
const proxy = new Proxy(target, handler);

// 트랩에서 true를 반환히면 true 가 아닌 proxy 인스턴스가 반환된다.
// 만약 false 면 false를 반환한다.
const obj = Object.preventExtensions(proxy); 

console.log(obj.point); // 100
console.log(Object.isExtensible(target)); // false

트랩 내부에서 Object.isExtensible(target)의 결과 false 일때
최종적으로 false를 반환하면 TypeError가 발생한다.
True 만 반환할 수 있다.

isExtensible() trap

Object.isExtensible 의 트랩이다.

Object.isExtensible
target의 확장 가능 여부를 반환한다.
Object.seal(), Object.freeze(), Object.preventExtension(),
Reflect.preventExtensions() 로 객체가 잠겨져있을 경우 false를 반환한다.

getPrototypeOf() trap

Object.getPrototypeOf() 메서드를 가로채는 데 사용

대상 객체의 프로토타입 지정을 가로채어 변환하거나,
특정 조건을 만족할때만 포로토타입에 접근할 수 있게하는 등의 동작을 구현할 수 있다.

class Point { 
	getPoint() { return 100; } // getPoint()는 Point가 아닌 Point.prototype에 할당된다.
}
const handler = {
	getPrototypeOf(target){
    	return target.prototype;
    }
};
const obj = new Proxy(Point, handler);
const proto = Object.getPrototypeOf(ojb);
console.log(proto.getPoint); // 100

트랩이 호출되는 형태

  • Object.getPrototypeOf()
  • proto
  • instanceOf
  • Object.prototype.isPrototypeOf()
  • Reflext.getPrototypeOf()
class Point {
	getPoint() { return 100; }
}
const handler = {
	getPrototypeOf(target) {
    	// this 는 handler 가 된다.
    	return this.list ? Array.prototype : target.prototype;
    }
};

const obj = new Proxy(Point, handler);
handler.list = true;
const proto = obj.__proto__; // Array.prototype
console.log(proto.map); // funciton map() { [native code] }

setPrototypeOf()

Object.getPrototypeOf() 메서드를 가로채는 데 사용

Object.setPrototypeOf()
__proto__는 정확하게는 Object.prototype에 있는 파라미터이다.
아래 그림에서는 직관적으로 보기위해 각 오브젝트 모두 포함시켰다.

class Book {setTitle() { return "책" } };
class Point {getPoint() { return 100; } };

Object.setPrototypeOf(Book, Point.prototype);

console.log(Book.prototype.getPoint); // undefined
console.log(Book.__proto__.getPoint); // getPoint() {return 100;}
const obj = new Book();
console.log(obj.getPoint); // getPoint() {return 100;}

트랩에서 true를 반환하지 않으면 에러가 발생한다.

class Book {setTitle() { return "책" } };
class Point {getPoint() { return 100; } };
const handler = {
	setPrototypeOf(target, proto) {
    	Object.setPrototypeOf(target, proto);
        return true;
    }
};
const obj = new Proxy(Book, handler);
Object.setPrototypeOf(obj. Point.prototype);
console.log(Book.prototype.getPoint); // undefined
console.log(Book.__proto__.getPoint); // getPoint() {return 100;}
console.log(obj.getPoint); // getPoint() {return 100;}

construct() trap

new 연산자의 트랩

new 연산자는 인스턴스를 생성하여 반환한다.

new Proxy로 인스턴스를 생성하고 이 인스턴스를 생성자 함수로 사용해서
다시 인스턴스를 만들어 낼 때 트랩이 호출된다.

파라미터

  • target : 대상 오브젝트
  • args : argumentsList, Array 또는 Array-like
  • proxy : newTarget(선택)

인스턴스의 초기값을 설정할때 Point 클래스의 constructor 에서 조건문으로 분기하지말고
트랩 내부에서 조건문을 사용하여 분기하면 클린 코드가 될 수 있다.

class Point {
	constructor(point){
    	this.point = point;
    }
};
const handler = {
	construct(target, args, proxy) { // args = [100, "add", 300]
    	let point = args[0];
        if(Object.is(args[1], "add")) {
        	point + args[2];
        };
        return new target(point);
    }
};
const obj = new Proxy(Point, handler);
const pointObj = new obj(100, "add", 300); // construct trap 호출
console.log(pointObj.point);

apply() trap

함수에 프록시가 달려있고 그 프록시 객체로 함수를 실행했을때
가로채는 함수

파라미터

  • target : 호출할 함수
  • that(선택) : this로 참조할 오브젝트
  • params(선택) 호출할 함수에 넘겨줄 파라미터

Proxy 인스턴스 호출로 인해 트랩이 실행되면 that에 값이 설정되지 않는다.

function getPoint(...values){
	return values.map((value) => {
    	return value + 10}
    });
|;
const handler = {
	apply(target, that, params){ // params = [100,200]
    	return target.apply(this, params);
    }
};
const obj = new Proxy(getPoint, handler);
console.log(obj(100,200));

트랩이 호출되는 형태

  • Function.prototype.apply()
  • Function.prototype.call()
  • proxy(..args) : Proxy 인스턴스
  • Reflect.apply()
function getPoint(...values){
	return values.map((value)) => {
    	return value + this.bonus;
    });
};
const handler = {
	apply(target, that, params){ // that(원본함수에 넘겨줄 this)은 add
    	return target.apply(that, params);
    }
};
const obj = new Proxy(getPoint, handler);
const add = {bonus : 10};
consolg.log(obj.apply(add, [100,200])); // add는 this 가 된다. 배열은 getPoint()로 넘겨줄 파라미터이다.
console.log(obj.call(add,100,200)); // 이하 동일

Ownkeys() trap

Object.getOwnPropertyNames()의 트랩이다.

target의 모든 key를 배열로 반환한다.
[[Configurable]] 이 false거나 오브젝트가 확장 불가이면 반환이 안된다.

const target = {};
Object.defineProperties(target, {
	point: {value: 100, enumerable: true},
    bonus: {value: 200} // enumerable, configurable, writable 등의 속성을 명시하지 않으면, 기본값은 false
}
const handler = {
	ownKeys(target){
    	return Object.getOwnPropertyNames(target);
    }
};
const obj = new Proxy(target, handler);
console.log(Object.getOwnPropertyNames(obj)); // [point, bonus], 트랩 호출
console.log(Object.keys(obj)); // [point], 트랩 호출, keys() 는 enumerable: true 인 프로퍼티만 출력한다.

트랩이 호출되는 형태

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • Reflect.ownKeys()

getOwnPropertyDescriptor() trap

Object.getOwnPropertyDesciptor() 의 트랩이다.
프로퍼티 디스크립터를 반환한다.

const target {};
Object.defineProperty(target, "point", {value: 100, configurable: true});

const handler ={
	getOwnPropertyDescriptor(target, key){
    	const desc = Object.getOwnPropertyDescriptor(target,key);
        if (desc.configurable)
        	return { value: 300, configurable: true };
         return desc;
    }
};
const obj = new Proxy(target, handler);
console.log(Object.getOwnPropertyDescriptor(obj, "point"); // 300        
profile
열정, 끈기, 집념의 Frontend Developer

0개의 댓글