Baseline Widely available
El objeto Proxy
permite crear un intermediario para otro objeto, el cual puede interceptar y redefinir operaciones fundamentales para dicho objeto.
Un Proxy
se crea con dos parámetros:
target
: el objeto original que se quiere envolver.handler
: un objeto que define cuáles operaciones serán interceptadas y cómo redefinir dichas operaciones.Por ejemplo, este código define un objeto simple que tiene solo dos propiedades, y un manipulador más simple aún que no tiene propiedades:
const target = {
message1: "hello",
message2: "everyone",
};
const handler1 = {};
const proxy1 = new Proxy(target, handler1);
Ya que el manipulador está vacÃo, este proxy se comporta justo como el objeto original:
console.log(proxy1.message1); // hello
console.log(proxy1.message2); // everyone
Para personalizar el intermediario, definimos funciones en el objeto manipulador:
const target = {
message1: "hello",
message2: "everyone",
};
const handler2 = {
get: function (target, prop, receiver) {
return "world";
},
};
const proxy2 = new Proxy(target, handler2);
Aquà hemos provisto una implementación del manipulador get()
, el cual intercepta los intentos de acceder a las propiedades del objeto envuelto.
Las funciones manipuladoras son llamadas a menudo trampas, probablemente porque atrapan las llamadas al objeto envuelto. La trampa simple de arriba en handler2
redefine todos los accesores de propiedades:
console.log(proxy2.message1); // world
console.log(proxy2.message2); // world
Con la ayuda de la clase Reflect
podemos darle a algunos accesores el comportamiento original y redefinir otros:
const target = {
message1: "hello",
message2: "everyone",
};
const handler3 = {
get: function (target, prop, receiver) {
if (prop === "message2") {
return "world";
}
return Reflect.get(...arguments);
},
};
const proxy3 = new Proxy(target, handler3);
console.log(proxy3.message1); // hello
console.log(proxy3.message2); // world
Constructor
Proxy()
Crea un nuevo objeto Proxy
.
Proxy.revocable()
Crea un objeto Proxy
revocable.
En este ejemplo, el número 37
es devuelto como valor pordefecto cuando el nombre de propiedad no está en el objeto. Se realiza usando el manipulador get()
.
const handler = {
get: function (obj, prop) {
return prop in obj ? obj[prop] : 37;
},
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b);
// 1, undefined
console.log("c" in p, p.c);
// false, 37
Proxy sin modificaciones
En este ejemplo se usa un objeto nativo de JavaScript para el cual el proxy reenviará todas las operaciones que se le apliquen.
const target = {};
const p = new Proxy(target, {});
p.a = 37;
// operación reenviada al objeto envuelto
console.log(target.a);
// 37
// (¡La operación ha sido reenviada correctamente!)
Nótese que mientras que esto funciona para objetos JavaScript, no lo hace para objetos nativos del navegador como Elementos del DOM.
ValidaciónCon un Proxy
, puedes puedes validar fácilmente el valor enviado para un objeto. Este ejemplo usa el manipulador set()
.
let validator = {
set: function (obj, prop, value) {
if (prop === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("La edad no es un entero");
}
if (value > 200) {
throw new RangeError("La edad parece inválida");
}
}
// El comportamiento por defecto es almacenar el valor
obj[prop] = value;
// Indica éxito
return true;
},
};
const person = new Proxy({}, validator);
person.age = 100;
console.log(person.age); // 100
person.age = "young"; // Lanza una excepción
person.age = 300; // Lanza una excepción
Extendiendo el constructor
Una función intermediaria podrÃa fácilmente extender un constructor con un nuevo constructor. Este ejemplo usa los manipuladores construct()
y apply()
.
function extend(sup, base) {
base.prototype = Object.create(sup.prototype);
base.prototype.constructor = new Proxy(base, {
construct: function (target, args) {
var obj = Object.create(base.prototype);
this.apply(target, obj, args);
return obj;
},
apply: function (target, that, args) {
sup.apply(that, args);
base.apply(that, args);
},
});
return base.prototype.constructor;
}
var Person = function (name) {
this.name = name;
};
var Boy = extend(Person, function (name, age) {
this.age = age;
});
Boy.prototype.gender = "M";
var Peter = new Boy("Peter", 13);
console.log(Peter.gender); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13
Manipulando nodos del DOM
A veces querrás alternar algún atributo o clase de dos elementos distintos. En este ejemplo se explica cómo lo puedes hacer usando el manipulador set()
.
let view = new Proxy({
selected: null
},
{
set: function(obj, prop, newval) {
let oldval = obj[prop];
if (prop === 'selected') {
if (oldval) {
oldval.setAttribute('aria-selected', 'false');
}
if (newval) {
newval.setAttribute('aria-selected', 'true');
}
}
// El comportamiento por defecto es almacenar el valor
obj[prop] = newval;
// Indica éxito
return true;
}
});
let i1 = view.selected = document.getElementById('item-1'); //da error aquÃ, i1 es null
console.log(i1.getAttribute('aria-selected'));
// 'true'
let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected'));
// 'false'
console.log(i2.getAttribute('aria-selected'));
// 'true'
Note: even if selected: !null, then giving oldval.setAttribute is not a function
El objeto intermediario products
evalúa el valor pasado y lo convierte en un array de ser necesario. El objeto también soporta una propiedad extra llamada latestBrowser
tanto como getter y como setter.
let products = new Proxy(
{
browsers: ["Internet Explorer", "Netscape"],
},
{
get: function (obj, prop) {
// Una propiedad extra
if (prop === "latestBrowser") {
return obj.browsers[obj.browsers.length - 1];
}
// El comportamiento por defecto es retornar el valor
return obj[prop];
},
set: function (obj, prop, value) {
// Una propiedad extra
if (prop === "latestBrowser") {
obj.browsers.push(value);
return true;
}
// Convierte el valor si no es un array
if (typeof value === "string") {
value = [value];
}
// El comportamiento por defecto es almacenar el valor
obj[prop] = value;
// Indica éxito
return true;
},
},
);
console.log(products.browsers);
// ['Internet Explorer', 'Netscape']
products.browsers = "Firefox";
// pasa una cadena (por error)
console.log(products.browsers);
// ['Firefox'] <- no hay problema, el valor es un arreglo
products.latestBrowser = "Chrome";
console.log(products.browsers);
// ['Firefox', 'Chrome']
console.log(products.latestBrowser);
// 'Chrome'
Buscando un elemento de un arreglo por su propiedad
Este proxy extiende un arreglo con ciertas funcionalidades utilitarias. Como se puede ver, puedes "definir" propiedades de manera flexible sin usar Object.defineProperties()
. Este ejemplo se puede adaptar para encontrar una fila de una tabla por su celda. En dicho caso, el target serÃa table.rows
.
let products = new Proxy(
[
{ name: "Firefox", type: "browser" },
{ name: "SeaMonkey", type: "browser" },
{ name: "Thunderbird", type: "mailer" },
],
{
get: function (obj, prop) {
// El comportamiento por defecto es retornar al valor; prop generalmente es un número
if (prop in obj) {
return obj[prop];
}
// Obtiene el número de productos; un alias de products.length
if (prop === "number") {
return obj.length;
}
let result,
types = {};
for (let product of obj) {
if (product.name === prop) {
result = product;
}
if (types[product.type]) {
types[product.type].push(product);
} else {
types[product.type] = [product];
}
}
// Obtiene un producto por su nombre
if (result) {
return result;
}
// Obtiene productos por tipo
if (prop in types) {
return types[prop];
}
// Obtiene los tipos de productos
if (prop === "types") {
return Object.keys(types);
}
return undefined;
},
},
);
console.log(products[0]); // { name: 'Firefox', type: 'browser' }
console.log(products["Firefox"]); // { name: 'Firefox', type: 'browser' }
console.log(products["Chrome"]); // undefined
console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types); // ['browser', 'mailer']
console.log(products.number); // 3
Un ejemplo con todas las trampas
Para crear un ejemplo con la lista completa de trampas
, con motivos didácticos, intentaremos intervenir un objeto no-nativo que se ajusta particularmente a este tipo de operación: el objeto global docCookies
creado por un simple marco de cookies.
/*
var docCookies = ... obtén el objeto "docCookies" aquÃ:
https://reference.codeproject.com/dom/document/cookie/simple_document.cookie_framework
*/
var docCookies = new Proxy(docCookies, {
get: function (oTarget, sKey) {
return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
},
set: function (oTarget, sKey, vValue) {
if (sKey in oTarget) {
return false;
}
return oTarget.setItem(sKey, vValue);
},
deleteProperty: function (oTarget, sKey) {
if ((!sKey) in oTarget) {
return false;
}
return oTarget.removeItem(sKey);
},
ownKeys: function (oTarget, sKey) {
return oTarget.keys();
},
has: function (oTarget, sKey) {
return sKey in oTarget || oTarget.hasItem(sKey);
},
defineProperty: function (oTarget, sKey, oDesc) {
if (oDesc && "value" in oDesc) {
oTarget.setItem(sKey, oDesc.value);
}
return oTarget;
},
getOwnPropertyDescriptor: function (oTarget, sKey) {
var vValue = oTarget.getItem(sKey);
return vValue
? {
value: vValue,
writable: true,
enumerable: true,
configurable: false,
}
: undefined;
},
});
/* Pruebas de cookies */
console.log((docCookies.my_cookie1 = "Primer valor"));
console.log(docCookies.getItem("my_cookie1"));
docCookies.setItem("my_cookie1", "Valor cambiado");
console.log(docCookies.my_cookie1);
Especificaciones Compatibilidad con navegadores Véase también
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4