Questa traduzione è incompleta. Collabora alla traduzione di questo articolo dall’originale in lingua inglese.

L'oggetto proxy è utilizzato per definire comportamenti personalizzati per operazioni fondamentali (per esempio: ricerca delle proprietà, assegnazione, enumerazione, invocazione delle funzioni, ecc.).

Terminologia

handler
Oggetto placeholder, il quale contiene le trappole.
traps
I metodi che forniscono le proprietà di accesso. Questo è analogo al concetto di trappola nei sistemi operativi.
target
Oggetti, i quali i proxy virtualizzano (sostituiscono).  Le invarianti, riguardanti oggetti non estensibili o proprietà non configurabili sono veriticate contro l'obiettivo.

Sintassi

var p = new Proxy(target, handler);

Parametri

target
Un oggetto target da wrappare con il Proxy.
handler
Un oggetto, le quali proprietà sono funzioni che definiscono i comportamenti del proxy, quando un operazione viene effettuato su di esso.

Metodi

Proxy.revocable()
Crea un oggetto Proxy revocabile.

Metodi per l'handler object

L'oggetto handler è un oggetto placeholder, il quale contiene le trappole per il Proxy.

Esempi

Esempio base

In questo esempio di base, il numero 37, viene ritornato come valore di defualt, quando il nome della proprietà non è un oggetto. Utilizza il get handler.

var handler = {
    get: function(target, name) {
        return name in target ?
            target[name] :
            37;
    }
};

var 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

No-op forwarding proxy

In questo esempio usiamo un oggetto Javascript nativo, il quale, il nostro proxy inoltrerà tutte le operazioni che li sono state applicate.

var target = {};
var p = new Proxy(target, {});

p.a = 37; // operazioni inoltrate al target

console.log(target.a); // 37. Operazione inoltrata con successo

Validation

Con un proxy, puoi facilmente validare il valore passato per un oggetto. In questo esempio viene utilizzato il set handler.

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // Il comportamento di default per memorizzare il valore
    obj[prop] = value;

    return true;
  }
};

let person = new Proxy({}, validator);

person.age = 100;
console.log(person.age); // 100
person.age = 'young'; // Lancia un exception
person.age = 300; // Lancia un exception

Extending constructor

Una funzione proxy può facilmente estendere un costruttore con un nuovo costruttore. Questo esempio usa gli handler: construct e apply .

function extend(sup, base) {
  var descriptor = Object.getOwnPropertyDescriptor(
    base.prototype, 'constructor'
  );
  base.prototype = Object.create(sup.prototype);
  var handler = {
    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);
    }
  };
  var proxy = new Proxy(base, handler);
  descriptor.value = proxy;
  Object.defineProperty(base.prototype, 'constructor', descriptor);
  return proxy;
}

var Person = function(name) {
  this.name = name;
};

var Boy = extend(Person, function(name, age) {
  this.age = age;
});

Boy.prototype.sex = 'M';

var Peter = new Boy('Peter', 13);
console.log(Peter.sex);  // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age);  // 13

Manipulating DOM nodes

Alcune volte vorresti togliere un attributo o una classe di due elementi differenti. Qui è mostrato come è possibile con il set handler.

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');
      }
    }

    // Il comportamento di default per memorizzare il valore
    obj[prop] = newval;

    // Indicate success
    return true;
  }
});

let i1 = view.selected = document.getElementById('item-1');
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'

Value correction and an extra property

L'oggetto products del proxy valuta il valore passato e lo converte in un array se è necessario. L'oggetto anche supporta una proprietà extra chiamata latestBrowser, che ha sia getter che setter.

let products = new Proxy({
  browsers: ['Internet Explorer', 'Netscape']
},
{
  get: function(obj, prop) {
    // An extra property
    if (prop === 'latestBrowser') {
      return obj.browsers[obj.browsers.length - 1];
    }

    // Il comportamento di default per ritornare il valore
    return obj[prop];
  },
  set: function(obj, prop, value) {
    // An extra property
    if (prop === 'latestBrowser') {
      obj.browsers.push(value);
      return true;
    }

    // Converte il valore se non è un array
    if (typeof value === 'string') {
      value = [value];
    }

    // Il comportamento di default per memorizzare il valore
    obj[prop] = value;

    // Indicate success
    return true;
  }
});

console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox'; // passa una stringa (per sbaglio)
console.log(products.browsers); // ['Firefox'] <- nessun problema, il valore passato è un array

products.latestBrowser = 'Chrome';
console.log(products.browsers); // ['Firefox', 'Chrome']
console.log(products.latestBrowser); // 'Chrome'

Trovare un oggetto in un array dalla sua proprietà

Questo proxy estende un array con alcune caratteristiche di utilità. Come puoi notare, puoi facilmente definire le proprietà senza utilizzare Object.defineProperties. Questo esempio può essere adattato per trovare una riga di una tabella dalla sua cella. In questo caso il target sarà table.rows.

let products = new Proxy([
  { name: 'Firefox', type: 'browser' },
  { name: 'SeaMonkey', type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
],
{
  get: function(obj, prop) {
    // Il comporamento di default per ritornare un valore; prop è di solito un integer
    if (prop in obj) {
      return obj[prop];
    }

    // Ottieni il numero di prodotti; un alias di 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];
      }
    }

    // Ottieni un prodotto dal nome
    if (result) {
      return result;
    }

    // Ottieni un prodotto dal tipo
    if (prop in types) {
      return types[prop];
    }

    // Ottieni i tipi di prodotto
    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

Una lista completa di traps

Prima di creare una lista di traps per scopi didattici, proveremo a proxare un oggetto non nativo, che è particolarmente adatto a questo tipo di operazioni: il docCookies è un oggetto glocale creato da the "little framework" published on the document.cookie page.

/*
  var docCookies = ... get the "docCookies" object here:  
  https://developer.mozilla.org/en-US/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
*/

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);
  },
  enumerate: function (oTarget, sKey) {
    return oTarget.keys();
  },
  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;
  },
});

/* Cookies test */

console.log(docCookies.my_cookie1 = 'First value');
console.log(docCookies.getItem('my_cookie1'));

docCookies.setItem('my_cookie1', 'Changed value');
console.log(docCookies.my_cookie1);

Specifications

Specification Status Comment
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Proxy' in that specification.
Standard Initial definition.
ECMAScript 2016 (ECMA-262)
The definition of 'Proxy' in that specification.
Standard  
ECMAScript 2017 (ECMA-262)
The definition of 'Proxy' in that specification.
Standard  
ECMAScript Latest Draft (ECMA-262)
The definition of 'Proxy' in that specification.
Living Standard  

Browser compatibility

FeatureChromeEdgeFirefoxInternet ExplorerOperaSafari
Basic support491218 No3610
revocable Yes Yes34 No Yes10
handler.apply491218 No3610
handler.construct491218 No3610
handler.defineProperty491218 No3610
handler.deleteProperty491218 No3610
handler.enumerate No No37 — 47 No No No
handler.get491218 No3610
handler.getOwnPropertyDescriptor491218 No3610
handler.getPrototypeOf No No49 No No No
handler.has491218 No3610
handler.isExtensible ? ?31 No ? ?
handler.ownKeys4912181 No3610
handler.preventExtensions491222 No3610
handler.set491218 No3610
handler.setPrototypeOf ? ?49 No ? ?
FeatureAndroid webviewChrome for AndroidEdge mobileFirefox for AndroidIE mobileOpera AndroidiOS Safari
Basic support4949 Yes18 No3610
revocable Yes Yes Yes34 No Yes10
handler.apply4949 Yes18 No3610
handler.construct4949 Yes18 No3610
handler.defineProperty4949 Yes18 No3610
handler.deleteProperty4949 Yes18 No3610
handler.enumerate No No No37 — 47 No No No
handler.get4949 Yes18 No3610
handler.getOwnPropertyDescriptor4949 Yes18 No3610
handler.getPrototypeOf No No No49 No No No
handler.has4949 Yes18 No3610
handler.isExtensible ? ? ?31 No ? ?
handler.ownKeys4949 Yes181 No3610
handler.preventExtensions4949 Yes22 No3610
handler.set4949 Yes18 No3610
handler.setPrototypeOf ? ? ?49 No ? ?

1. In Firefox 42, the implementation got updated to reflect the final ES2015 specification: The result is now checked if it is an array and if the array elements are either of type string or of type symbol. Enumerating duplicate own property names is not a failure anymore.

See also

Licensing note

Some content (text, examples) in this page has been copied or adapted from the ECMAScript wiki which content is licensed CC 2.0 BY-NC-SA.

Tag del documento e collaboratori

 Hanno collaborato alla realizzazione di questa pagina: federicoviceconti
 Ultima modifica di: federicoviceconti,