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.