Как стать автором
Обновить

JS. Proxy. Подводный камень, о котором нужно знать

Время на прочтение2 мин
Количество просмотров5.4K
Эта статья будет интересна тем кто использует Proxy, для реактивности или рефлексии.
Поведение JS методов, нам хорошо знакома если мы просто используем их в рамках объекта.
Если метод передается через свойство другому объект, то он работает с тем this, который определен в рамках другого объекта.

let obj1={prop1:'HEllo',method1(){console.log(this);}}
let obj2={method2:obj1.method1};
obj2.method2();

Это необходимо ясно понимать при использовании Proxy.

class MyProxy{
	constructor(target){
		return new Proxy(target,this);
	}
	get(target,prop){
                console.log(target,prop); //Label1
		return target[prop];
	}
}
class PrimitiveType
{
	constructor(target,prop)
	{
	    this.target=target;
	    this.prop=prop;
	}
	get(){
		console.log(this);// Label2  
		return this.target[this.prop];
	}
}
prim=new PrimitiveType({a:'Привет'},'a');
proxy= new MyProxy(prim);
proxy.get();

Результатом будет что console.log(Label2); выдаст нам Proxy объект, после которого Proxy сработает и на target и на prop (см Label1); но код же вроде как работает. Что париться.

Метод начинает общаться с объектом (this) через Proxy. Это удобно и закономерно когда пишем рефлексию (отражение свойств объекта и изменения поведения не изменяя объект). Но если это нам не нужно и нам нужно чтобы метод работал конкретно с объектом target, как тут быть? Зачем нам замедлять код?

Тем более что если внесем больше логики, например фильтры свойств и др, код может случайно загнуться. А при написании реактивного кода, идет «зашкаливание». (Например при запросе метода и последующем его исполнении, метод запрашивает свойства через прокси на которые уже повешены события ). Т.е события начинают срабатывать там где не надо и их не ждали.

Как исправить


Как понял this уже переопределен для метода до вызова Handler.get в Proxy. Надо просто снова его переопределить следующим образом:

let answer=target[prop];
if(typeof target[prop] ==='function'){
        answer=target[prop].bind(target);
}

Получим вот такой код:

class MyProxy{
	constructor(target){
		return new Proxy(target,this);
	}
	get(target,prop){ // по уму название ему valueOf.  Но для наглядного поведения ему имя get 
        let answer=target[prop];
        if(typeof target[prop] ==='function'){
                answer=target[prop].bind(target);
        }
		return answer;
	}
}
class PrimitiveType
{
	constructor(target,prop)
	{
	    this.target=target;
	    this.prop=prop;
	}
	get(){
		console.log(this);
		return this.target[this.prop];
	}
}
prim=new PrimitiveType({a:'Привет'},'a');
proxy= new MyProxy(prim);
proxy.get();

Напоследок в качестве бонуса.


Cоздание цепочки реактивности/рефлексии. Каждый вложенный объект будет являться Proxy:

class MyProxy{
	constructor(target){
		return new Proxy(target,this);
	}
	get(target,prop){
		let answer;
        let tp=target[prop];// так необходимо если target - Proxy или target[prop] -getter
		
		if(typeof tp==='object' && tp!==null){
			answer =new MyProxy(tp);
		} else 
		if(typeof tp ==='function'){ // Если необходима реактивность.  Для рефлексии  стоит убрать этот блок
        	answer=tp.bind(target);
        } else {
			answer=tp;
		}
		return answer;
	}
}
class PrimitiveType
{
	constructor(target,prop)
	{
	    this.target=target;
	    this.prop=prop;
	}
	valueOf(){
		console.log(this);
		return this.target[this.prop];
	}
}
prim=new PrimitiveType({a:'Привет'},'a');
qwer={q:prim};
proxy= new MyProxy(qwer);
proxy.q

Спасибо за внимание!
Теги:
Хабы:
+11
Комментарии3

Публикации

Истории

Работа

Ближайшие события