Proxy

This is an experimental technology, part of the ECMAScript 6 (Harmony) proposal.
Because this technology's specification has not stabilized, check the compatibility table for usage in various browsers. Also note that the syntax and behavior of an experimental technology is subject to change in future version of browsers as the spec changes.

Shrnutí

Objekty proxy umožnují pro základní akce, jako je např. čtení vlastností, volání metod atd., definovat funkce, ktreré ovlivní jejich průběh.

Poznámka: Slovo „proxy“ se neskloňuje a obvykle se používá v ženském rodu.

Terminologie

správce
Objekt, který obsahuje funkce definující chování proxy – např. funkce get je spuštěna pokaždé, když se proces pokusí o čtení vlastnosti. V angličtině se správce jmenuje „handler“ a jeho metody „traps“ – jako ekvivalent pro traps v operačních systémech.
zdroj
Objekt, který je uvnitř proxy, většinou slouží k ukládání dat pro metody správce. Proxy, která nemá definované žádné metody správce, se bude tvářit, že je zdroj – všechny akce budou přesměrovány a např. přepsání vlastnosti proxy bude znamenat přepsání vlastnosti zdroje. V angličtině se pro zdroj používá označení „target“.

Syntaxe

var p = new Proxy(zdroj, správce);

Parametry

zdroj
Objekt, který bude použitý jako zdroj proxy. Může to být jakýkoliv typ objektu, včetně pole, funkce, nebo dokonce jiné proxy.
správce
Objekt obsahující funkce, které definují chování v případě, že je na proxy provedena určitá akce.

Metody

Proxy.revocable()
Vytvoří deaktivovatelnou proxy, tj. vrátí objekt proxy a funkci, která ji vypne.

Metody správce

Správce obsahuje metody definující chování proxy.

All traps are optional. If a trap has not been defined, the default behavior is to forward the operation to the target.

handler.getPrototypeOf()
A trap for Object.getPrototypeOf.
handler.setPrototypeOf()
A trap for Object.setPrototypeOf.
handler.isExtensible()
A trap for Object.isExtensible.
handler.preventExtensions()
A trap for Object.preventExtensions.
handler.getOwnPropertyDescriptor()
A trap for Object.getOwnPropertyDescriptor.
handler.defineProperty()
A trap for Object.defineProperty.
handler.has()
A trap for the in operator.
handler.get()
A trap for getting property values.
handler.set()
A trap for setting property values.
handler.deleteProperty()
A trap for the delete operator.
handler.enumerate()
A trap for for...in statements.
handler.ownKeys()
A trap for Object.getOwnPropertyNames.
handler.apply()
A trap for a function call.
handler.construct()
A trap for the new operator.

Some non-standard traps are obsolete and have been removed.

Příklady

Základní použití

V tomto jednoduchém příkladě proxy vrací hodnotu vlastnosti zdroje, nebo číslo 37 v případě, že vlastnost není definována. Je použita metoda správce „get“.

var správce = {
    get: function(zdroj, vlastnost){
        if(vlastnost in zdroj){
            return zdroj[vlastnost];
        }else{
            return 37;
        }
    }
};

var p = new Proxy({}, správce);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37

Pasivní přesměrování

V tomto příkladu není definována žádná metoda na správci, proxy tedy přesměruje veškeré akce na zdrojový objekt.

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

p.a = 37; // akce přesměrována na zdroj

console.log(zdroj.a); // 37

Odmítnutí údajů

S proxy lze snadno docílit např. toho, že objekt odmítne přijmout určité hodnoty a místo toho vyhodí chybu. Tento příklad používá metodu správce „set“.

let ověření = {
  set: function(zdroj, vlastnost, hodnota) {
    if (vlastnost === 'věk') {
      if (!Number.isInteger(hodnota)) {
        throw new TypeError('Věk není celé číslo');
      }
      if (hodnota > 200) {
        throw new RangeError('Věk vypadá nesprávně');
      }
    }

    // Obvyklé chování je uložit hodnotu
    zdroj[vlastnost] = hodnota;
  }
};

let člověk = new Proxy({}, ověření);

člověk.věk = 100;
console.log(člověk.věk); // 100
člověk.věk = 'mladý'; // Vyhodí chybu
člověk.věk = 300; // Vyhodí chybu

Rozšiřování konstruktoru

Proxy lze snadno použít pro rozšiřování konstruktoru novým konstruktorem. Tento příklad používá metody správce „construct“ a „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

Manipulace s elementy DOM

Někdy potřebujete změnit atribut nebo třídu dvou různých elementů. Zde je návod používající metodu správce „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');
      }
    }

    // The default behavior to store the value
    obj[prop] = newval;
  }
});

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'

Oprava hodnot a dodatečná vlastnost

Proxy products automaticky změní hodnotu na pole, když je to potřeba. Také podporuje speciální vlastnost latestBrowser, kterou lze číst i zapisovat.

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];
    }

    // The default behavior to return the value
    return obj[prop];
  },
  set: function(obj, prop, value) {
    // An extra property
    if (prop === 'latestBrowser') {
      obj.browsers.push(value);
      return;
    }

    // Convert the value if it is not an array
    if (typeof value === 'string') {
      value = [value];
    }

    // The default behavior to store the value
    obj[prop] = value;
  }
});

console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox'; // pass a string (by mistake)
console.log(products.browsers); // ['Firefox'] <- no problem, the value is an array

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

Hledání v poli pomocí hodnot

Tato proxy rozšiřuje array o několik užitečných vlastností. Jak vidíte, můžete "definovat" vlastnosti i bez použití Object.defineProperties. Tento příklad lze trochu pozměněný použít pro vyhledávání řádků tabulky podle jejich buňek. V takovém případě by zrojový objekt byl table.rows.

let products = new Proxy([
  { name: 'Firefox', type: 'browser' },
  { name: 'SeaMonkey', type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
],
{
  get: function(obj, prop) {
    // The default behavior to return the value; prop is usually an integer
    if (prop in obj) {
      return obj[prop];
    }

    // Get the number of products; an alias of 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];
      }
    }

    // Get a product by name
    if (result) {
      return result;
    }

    // Get products by type
    if (prop in types) {
      return types[prop];
    }

    // Get product types
    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

Příklad s kompletním seznamem metod správce

Abychom pro účely výuky sestrojili seznam všech metod správce, zkusíme pomocí proxy vytvořit objekt dokonale přizpůsobený práci s cookies: globální objekt docCookies vytvořený podle frameworku publikovaného na stránce o document.cookie.

/*
  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 */

alert(docCookies.my_cookie1 = "First value");
alert(docCookies.getItem("my_cookie1"));

docCookies.setItem("my_cookie1", "Changed value");
alert(docCookies.my_cookie1);

Podpora prohlížečů

Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari
Basic support ? 18 (18) 12 ? ?
Feature Android Chrome for Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Basic support ? ? 18 (18) ? ? ?

Gecko specific notes

  • At present, Object.getPrototypeOf(proxy) unconditionally returns Object.getPrototypeOf(target), because the ES6 getPrototypeOf trap is not yet implemented (chyba 888969, chyba 888969).
  • Array.isArray(proxy) unconditionally returns Array.isArray(target) (chyba 1111785, chyba 1111785).
  • Object.prototype.toString.call(proxy) unconditionally returns Object.prototype.toString.call(target), because ES6 Symbol.toStringTag is not yet implemented (chyba 1114580).

Související

Licenční poznámka

Část stránky byla zkopírována z ECMAScript wiki, která je dostupná pod licencí CC 2.0 BY-NC-SA.

Štítky a přispěvatelé do dokumentace

 Přispěvatelé této stránky: m93a
 Poslední aktualizace od: m93a,