Podsumowanie czym jest Proxy w języku JavaScript i jak korzystać z różnych pułapek handlera. Do dzieła.
Ok, przykład handlera z get:
var person = {
firstName: "Jan",
lastName: "Kowalski",
_age: 32
};
let proxy = new Proxy(person, {
get(target, property, receiver) {
if(property.charAt(0) === "_") {
return undefined;
}
return Reflect.get(target, property, receiver);
}
});
Tu mamy pseudoprywatność. Jeżeli ukryjemy główny obiekt i pokażemy proxy, to nawet nie pseudo. Przypomnienie:
- target – obiekt, do którego utworzyliśmy proxy
- property – nazwa/symbol property, który ktoś próbuje wziąć
- receiver – sam obiekt proxy
Jeżeli pamiętamy nasze zabawy z localstroage, to tak, proxy może dać limitowaną możliwość rzucania metod obiektu target. Limitowaną, bo nie można przekazywać argumentów i takie metody używa się bez nawiasów.
Tu mamy ten fragment, oczywiście nie jest to najlepszy kod:
get(target, prop, receiver){
if(typeof target[prop] === 'function'){
return target[prop]();
}
if(target.exists === false)
return;
localStorage.setItem(target.name, target.value);
return target.value;
}
}
Natomiast warto mieć świadomość, że to możliwe. Ok, teraz przykład z MDN:
const monster1 = {
secret: 'easily scared',
eyeCount: 4,
};
const handler1 = {
get: function (target, prop, receiver) {
if (prop === 'secret') {
return `${target.secret.substring(0, 4)} ... shhhh!`;
}
return Reflect.get(...arguments);
},
};
const proxy1 = new Proxy(monster1, handler1);
console.log(proxy1.eyeCount);
// Expected output: 4
console.log(proxy1.secret);
// Expected output: "easi ... shhhh!"
Ok, teraz set, przykład z MDN:
const monster1 = { eyeCount: 4 };
const handler1 = {
set(obj, prop, value) {
if (prop === 'eyeCount' && value % 2 !== 0) {
console.log('Monsters must have an even number of eyes');
} else {
return Reflect.set(...arguments);
}
},
};
const proxy1 = new Proxy(monster1, handler1);
proxy1.eyeCount = 1;
// Expected output: "Monsters must have an even number of eyes"
console.log(proxy1.eyeCount);
// Expected output: 4
proxy1.eyeCount = 2;
console.log(proxy1.eyeCount);
// Expected output: 2
Btw, nikt nam nie każe korzystać z Reflect api, mamy obiekt oraz symbol property, jak nie lubimy Relfect to nie ma problemu.
Ok, has trap, też może się czasem przydać, przykład z MDN:
const handler1 = {
has(target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
},
};
const monster1 = {
_secret: 'easily scared',
eyeCount: 4,
};
const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);
// Expected output: true
console.log('_secret' in proxy1);
// Expected output: false
console.log('_secret' in monster1);
// Expected output: true
A teraz przykłady apply i construct. Najpierw zróbmy klasę:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
sayHello() {
return `${this.firstName} ${this.lastName}`;
}
}
Wiemy, że klasy (w odróżnieniu od funkcji konstruującej) nie można wywołać bez new. Wiemy też, że wszystko w JS to jest funkcja i obiekt.
Ok, pułapki funkcyjne:
- apply – gdy zawołamy obiekt jako funkcję bez new
- construct – gdy zawołamy obiekt jako funkcję z new
Ok, przykład:
const PersonProxy = new Proxy(Person, {
apply(target, thisArg, argumentsList) {
return new target(...argumentsList);
}
});
let person = PersonProxy("Jan", "Kowalski");
Dodam, że w funkcjach konstuujących nie musieliśmy się tak bawić:
function Person(name, age){
if(!new.target)
return new Person(name, age);
this.name = name;
this.age = age;
}
let jim = Person("Jim", 20);
console.log(jim);
//Object { name: "Jim", age: 20 }
W klasach musimy.
Ok, teraz construct:
const ReverseProxy = new Proxy(Person, {
construct(target, args){
return new target(args[1], args[0]);
}
});
let person2 = new ReverseProxy("Kowalski", "Jan");
console.log(person2.sayHello());
//Jan Kowalski
Dodam, że nie jest możliwe zrobienie proxy przechwytującego metody danego obiektu. W metodach bez argumentów dało się to jeszcze objeść przez get, a niedługo poznamy jeszcze lepszy mechanizm.