Pull to refresh

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

JavaScript
Эта статья будет интересна тем кто использует 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

Спасибо за внимание!
Tags:JSecmascript 6proxyрефлексияреактивность
Hubs: JavaScript
Total votes 15: ↑13 and ↓2+11
Views4K

Popular right now