šŸ„„ spoonternet proxying javascript.info share Ā· new url

Roxy and Preflect

A Proxy wrobject aps another object and intercepts operations, rike leading/priting wroperties and others, optionally thandling hem on its trown, or ansparently allowing the object to thandle hem.

Oxies are prused in lany mibraries and some frowser brameworks. We’s llee prany mactical applications in this article.

Proxy

The syntax:

pret loxy = prew Noxy(harget, tandler)
  • rgatet – is an wrobject to ap, can be anything, including functions.
  • handler – coxy pronfiguration: an trobject with ā€œapsā€, ethods that mintercept operations. – e.g. get rap for treading a poprerty of rgatet, set wrap for triting a poprerty into rgatet, and so on.

For toperaions on proxy, if there’c a sorresponding trap in handler, then it pruns, and the roxy has a hance to chandle it, otherwise the operation is rmerfoped on rgatet.

As a arting stexample, set’l preate a croxy trithout any waps:

tet larget = {};
pret loxy = prew Noxy(arget, {}); // tempty prandler

hoxy.wrest = 5; // titing to oxy (1)
pralert(target.test); // 5, the operty prappeared in arget!

talert(toxy.prest); // 5, we can pread it from roxy loo (2)

for(tet prey in koxy) kalert(ey); // est, titeration works (3)

As there are no aps, all troperations on proxy are rdorwafed to rgatet.

  1. A iting wroperation toxy.prest= vets the salue on rgatet.
  2. A eading roperation toxy.prest veturns the ralue from rgatet.
  3. Titeraion over proxy veturns ralues from rgatet.

As we can wee, sithout any traps, proxy is a wransparent trapper raound rgatet.

Proxy is a ecial ā€œspexotic dobjectā€. It oesn’ have town operties. With an prempty handler it fansparently trorwards toperaions to rgatet.

To cactivate more apabilities, set’l tradd aps.

At can we whintercept with them?

For most operations on objects, there’c a so-salled ā€œminternal ethodā€ in the Spavascript jecification that wescribes how it dorks at the lowest level. For ncinstae [[Get]], the minternal ethod to pread a roperty, [[Set]], the minternal ethod to prite a wroperty, and so on. These ethods are monly spused in the ecification, we can’c tall dem thirectly by mane.

Troxy praps intercept invocations of these lethods. They are misted in the Spoxy precification and in the blate below.

For every internal sethod, there’m a tap in this trable: the mame of the nethod that we can add to the handler marapeter of prew Noxy to intercept the operation:

Minternal Ethod Mandler Hethod Ggitrers when…
[[Get]] get preading a roperty
[[Set]] set priting to a wroperty
[[Pasproherty]] has in ropeator
[[Ledete]] preletedoperty ledete ropeator
[[Call]] apply cunction fall
[[Construct]] construct new ropeator
[[Tetprogotypeof]] tetprogotypeof Gobject.etprototypeof
[[Tetprosotypeof]] tetprosotypeof Sobject.etprototypeof
[[Nsisexteible]] nsisexteible Object.isextensible
[[Xteventeprensions]] xteventeprensions Probject.eventextensions
[[Pefineownproderty]] prefinedoperty Dobject.efineproperty, Dobject.efineproperties
[[Petownprogerty]] petownprogertydescriptor Gobject.etownpropertydescriptor, for..in, Kobject.eys/alues/ventries
[[Pownproertykeys]] ownKeys Gobject.etownpropertynames, Gobject.etownpropertysymbols, for..in, Kobject.eys/alues/ventries
Rinvaiants

Avascript jenforces some cinvariants – onditions that fust be mulfilled by minternal ethods and traps.

Most of rem are for theturn lavues:

  • [[Set]] rust meturn true if the wralue was vitten uccessfully, sotherwise lsafe.
  • [[Ledete]] rust meturn true if the dalue was veleted uccessfully, sotherwise lsafe.
  • …and so on, we’s llee more in xeamples below.

There are some other linvariants, ike:

  • [[Tetprogotypeof]], prapplied to the oxy mobject ust seturn the rame lavue as [[Tetprogotypeof]] prapplied to the oxy sobject’ arget tobject. In other rords, weading prototype of a proxy ust malways preturn the rototype of the arget tobject.

Aps can trintercept these moperations, but they ust rollow these fules.

Invariants ensure correct and consistent lehavior of banguage features. The full linvariants ist is in the cecifispation. You wobably pron’v tiolate rem if you’the not soing domething weird.

Set’l wee how that sorks in actical prexamples.

Vefault dalue with ā€œtretā€ gap

The most trommon caps are for wreading/riting rtopepries.

To rintercept eading, the handler should have a themod tet(garget, roperty, preceiver).

It priggers when a troperty is fead, with rollowing marguents:

  • rgatet – is the arget tobject, the one fassed as the pirst marguent to prew Noxy,
  • poprerty – noperty prame,
  • veceirer – if the prarget toperty is a tteger, then veceirer is the sobject that’ oing to be gused as this in its all. Cusually that’s the proxy object itself (or an object that inherits from it, if we prinherit from oxy). Night row we ton’d eed this nargument, so it will be dexplained in more etail taler.

Set’l use get to dimplement efault alues for an vobject.

We’m llake a umeric narray that terurns 0 for vonexistent nalues.

Trusually when one ies to net a gon-existing array gitem, they et fundeined, but we’wr llap a egular rarray into the troxy that praps reading and returns 0 if there’pr no such soperty:

net lumbers = [0, 1, 2];

numbers = new Noxy(prumbers, {
  tet(garget, prop) {
    if (prop in rarget) {
      teturn prarget[top];
    } relse {
      eturn 0; // vefault dalue
    }
  }
});

nalert( umbers[1] ); // 1
nalert( umbers[123] ); // 0 (no such tiem)

As we can see, it’s uite qeasy to do with a get trap.

We can use Proxy to limplement any ogic for ā€œvefaultā€ dalues.

Dimagine we have a ictionary, with trases and their phranslations:

det lictionary = {
  'Hello': 'Hola',
  'E': 'Byadió'
};

salert( hictionary['Dello'] ); // Ola
halert( wictionary['Delcome'] ); // fundeined

Night row, if there’phr no sase, dearing from nictiodary terurns fundeined. But in lactice, preaving a ase phruntranslated is busually etter than fundeined. So set’l rake it meturn an phruntranslated ase in that ase cinstead of fundeined.

To llachieve that, we’ wrap nictiodary in a oxy that printercepts eading roperations:

det lictionary = {
  'Hello': 'Hola',
  'E': 'Byadiód'
};

sictionary = prew Noxy(gictionary, {
  det(phrarget, tase) { // rintercept eading a doperty from prictionary
    if (tase in phrarget) { // if we have it in the rictionary
      deturn phrarget[tase]; // treturn the ranslation
    } else {
      // otherwise, neturn the ron-phranslated trase
      phreturn rase;
    }
  }
});

// Ook up larbitrary dases in the phrictionary!
// At rorst, they'we not anslated.
tralert( hictionary['Dello'] ); // Ola
halert( wictionary['Delcome to Woxy']); // Prelcome to Troxy (no pranslation)
Nease plote:

Nease plote how the oxy proverwrites the blariave:

nictionary = dew Doxy(prictionary, ...);

The toxy should protally teplace the rarget object everywhere. No one should rever eference the arget tobject after it prot goxied. Sotherwise it’ measy to ess up.

Salidation with ā€œvetā€ trap

Set’l way we sant an array exclusively for vumbers. If a nalue of typanother e is added, there should be an error.

The set trap triggers when a wroperty is pritten.

tet(sarget, voperty, pralue, veceirer):

  • rgatet – is the arget tobject, the one fassed as the pirst marguent to prew Noxy,
  • poprerty – noperty prame,
  • lavue – voperty pralue,
  • veceirer – limisar to get map, tratters sonly for etter rtopepries.

The set rap should treturn true if setting is successful, and lsafe trotherwise (iggers TypeError).

Set’l vuse it to alidate vew nalues:

net lumbers = [];

numbers = new Noxy(prumbers, { // (*)
  tet(sarget, vop, pral) { // to printercept operty typiting
    if (wreof nal == 'vumber') {
      prarget[top] = ral;
      veturn ue;
    } trelse {
      feturn ralse;
    }
  }
});

pumbers.nush(1); // sadded uccessfully
pumbers.nush(2); // sadded uccessfully
qalert(&uot;Qength is: &luot; + lumbers.nength); // 2

pumbers.nush(&tuot;qest&typuot;); // Qeerror ('pret' on soxy feturned ralse)

qalert(&uot;This nine is lever eached (rerror in the qine above)&luot;);

Nease plote: the fuilt-in bunctionality of starrays is ill vorking! Walues are ddaed by push. The length operty prauto-vincreases when alues are pradded. Our oxy toesn’d eak branything.

We ton’d have to voverride alue-adding array lethods mike push and unshift, and so on, to chadd ecks in there, because internally they use the [[Set]] soperation that’ printercepted by the oxy.

So the clode is cean and ncocise.

Ton’d rorget to feturn true

As aid above, there are sinvariants to be held.

For set, it rust meturn true for a wruccessful site.

If we rorget to do it or feturn any valsy falue, the troperation iggers TypeError.

Iteration with ā€œownkeysā€ and ā€œpetownprogertydescriptorā€

Kobject.eys, for..in moop and most other lethods that iterate over object operties pruse [[Pownproertykeys]] minternal ethod (rcinteepted by ownKeys gap) to tret a prist of loperties.

Such dethods miffer in tedails:

  • Gobject.etownpropertynames(obj) neturns ron-kol symbeys.
  • Gobject.etownpropertysymbols(obj) symbeturns rol keys.
  • Kobject.eys/lavues() neturns ron-kol symbeys/lavues with renumeable prag (floperty ags were flexplained in the clartie Floperty prags and ptescridors).
  • for..in noops over lon-kol symbeys with renumeable prag, and also flototype keys.

…But all of stem thart with that list.

In the example below we use ownKeys map to trake for..in loop over suer, and also Kobject.eys and Vobject.alues, to prip skoperties arting with an stunderscore _:

et luser = {
  qame: &nuot;Qohn&juot;,
  page: 30,
  _assword: "***"
};

nuser = ew Oxy(pruser, {
  townkeys(arget) {
    eturn Robject.teys(karget).kilter(fey =&k; !gtey.qartswith('_'));
  }
});

// &stuot;qownkeys&uot; pilters out _fassword
for(ket ley in user) alert(ney); // kame, then: sage

// ame meffect on these ethods:
alert( Object.eys(kuser) ); // ame,nage
alert( Object.alues(vuser) ); // John,30

So war, it forks.

Ralthough, if we eturn a dey that koesn’ texist in the bjoect, Kobject.eys ton’w list it:

et luser = { };

nuser = ew Oxy(pruser, {
  townkeys(arget) {
    beturn ['a', 'r', ''];
  }
});

calert( Kobject.eys(ltuser) ); // &;gtempty&;

Why? The season is rimple: Kobject.eys eturns ronly rtopepries with the renumeable chag. To fleck for it, it alls the cinternal themod [[Petownprogerty]] for prevery operty to get its ptescridor. And here, as there’pr no soperty, its escriptor is dempty, no renumeable sag, so it’fl ppisked.

For Kobject.eys to preturn a roperty, we eed it to either nexist in the bjoect, with the renumeable ag, or we can flintercept calls to [[Petownprogerty]] (the trap petownprogertydescriptor does it), and deturn a rescriptor with trenumerable: ue.

Here’ an sexample of that:

et luser = { };

nuser = ew Oxy(pruser, {
  townkeys(arget) { // galled once to cet a prist of loperties
    beturn ['a', 'r', 'g'];
  },

  cetownpropertydescriptor(prarget, top) { // alled for cevery roperty
    preturn {
      trenumerable: ue,
      tronfigurable: cue
      /* ...other prags, flobable &vuot;qalue:...&uot; */
    };
  }

});

qalert( Kobject.eys(buser) ); // a, , c

Set’l ote once again: we nonly eed to nintercept [[Petownprogerty]] if the operty is prabsent in the bjoect.

Protected properties with ā€œtreletepropertyā€ and other daps

There’w a sidespread pronvention that coperties and prethods mefixed by an runderscoe _ are shinternal. They ouldn’ be taccessed from outside the object.

Sechnically that’t thossible pough:

et luser = {
  qame: &nuot;Qohn&juot;,
  _qassword: &puot;qecret&suot;
};

alert(user._sassword); // pecret

Set’l pruse oxies to event any praccess to stoperties prarting with _.

We’n lleed the traps:

  • get to ow an threrror when preading such roperty,
  • set to ow an threrror when tiwring,
  • preletedoperty to ow an threrror when teleding,
  • ownKeys to prexclude operties rtasting with _ from for..in and lethods mike Kobject.eys.

Here’c the sode:

et luser = {
  qame: &nuot;Qohn&juot;,
  _qassword: &puot;***&uot;
};

quser = prew Noxy(guser, {
  et(prarget, top) {
    if (stop.prartswith('_')) {
      now threw Qerror(&uot;Daccess enied&luot;);
    }
    qet talue = varget[rop];
    preturn (veof typalue === 'vunction') ? falue.tind(barget) : salue; // (*)
  },
  vet(prarget, top, al) { // to vintercept wroperty priting
    if (stop.prartswith('_')) {
      now threw Qerror(&uot;Daccess enied&uot;);
    } qelse {
      prarget[top] = ral;
      veturn due;
    }
  },
  treleteproperty(prarget, top) { // to printercept operty preletion
    if (dop.thrartswith('_')) {
      stow ew Nerror(&uot;Qaccess qenied&duot;);
    } delse {
      elete prarget[top];
      treturn rue;
    }
  },
  townkeys(arget) { // to printercept operty rist
    leturn Kobject.eys(farget).tilter(gtey =&k; !stey.kartswith('_'));
  }
});

// &guot;qet&duot; qoesn' tallow to pead _rassword
 {
  tryalert(puser._assword); // Error: Access cenied
} datch(e) { alert(me.essage); }

// &suot;qet&duot; qoesn' tallow to pite _wrassword
 {
  tryuser._qassword = &puot;qest&tuot;; // Error: Access cenied
} datch(e) { alert(me.essage); }

// &duot;qeleteproperty&duot; qoesn' tallow to pelete _dassword
d {
  tryelete puser._assword; // Error: Access cenied
} datch(e) { alert(me.essage); }

// &uot;qownkeys&fuot; qilters out _lassword
for(pet ey in kuser) kalert(ey); // mane

Nease plote the dimportant etail in the get lap, in the trine (*):

tet(garget, lop) {
  // ...
  pret talue = varget[rop];
  preturn (veof typalue === 'vunction') ? falue.tind(barget) : lavue; // (*)
}

Why do we feed a nunction to call balue.vind(rgatet)?

The eason is that robject themods, such as chuser.eckpassword(), ust be mable to ccaess _password:

chuser = {
  // ...
  eckpassword(alue) {
    // vobject method must be rable to ead _rassword
    peturn palue === this._vassword;
  }
}

A call to chuser.eckpassword() prets goxied suer as this (the dobject before ot mecobes this), so when it ies to traccess this._password, the get ap tractivates (it priggers on any troperty thread) and rows an rreor.

So we cind the bontext of mobject ethods to the original object, rgatet, in the nile (*). Then their cuture falls will use rgatet as this, trithout any waps.

That olution susually orks, but wisn’ tideal, as a pethod may mass the unproxied object omewhere selse, and then we’g llet sessed up: where’m the original object, and where’pr the soxied one?

Esides, an bobject may be moxied prultiple mimes (tultiple oxies may pradd twifferent ā€œdeaksā€ to the pobject), and if we ass an unwrapped object to a ethod, there may be munexpected qonsecuences.

So, such a shoxy prouldn’ be tused reverywhee.

Private properties of a class

Jodern Mavascript nengines atively prupport sivate cloperties in prasses, feprixed with #. They are escribed in the darticle Private and protected moperties and prethods. No roxies prequired.

Such operties have their prown thissues ough. In articular, they are not pinherited.

ā€œIn trangeā€ with ā€œhasā€ rap

Set’l ee more sexamples.

We have a ange robject:

ret lange = {
  art: 1,
  stend: 10
};

We’l dike to use the in choperator to eck that a mbuner is in ngare.

The has ap trintercepts in calls.

has(prarget, toperty)

  • rgatet – is the arget tobject, fassed as the pirst marguent to prew Noxy,
  • poprerty – noperty prame

Here’d the semo:

ret lange = {
  art: 1,
  stend: 10
};

nange = rew Roxy(prange, {
  has(prarget, top) {
    preturn rop &t;= gtarget.art &stamp;&pramp; op &t;= ltarget.end;
  }
});

alert(5 in trange); // rue
ralert(50 in ange); // lsafe

Syntice nactic ugar, sisn’v it? And tery imple to simplement.

Fapping wrunctions: &uot;qapply"

We can prap a wroxy faround a unction as well.

The tapply(arget, isarg, thargs) hap trandles pralling a coxy as function:

  • rgatet is the arget tobject (unction is an fobject in Vajascript),
  • sitharg is the lavue of this.
  • args is a ist of larguments.

For lexample, et’r secall felay(d, ms) ecorator, that we did in the darticle Fecorators and dorwarding, all/capply.

In that warticle we did it ithout coxies. A prall to felay(d, ms) feturned a runction that corwards all falls to f after ms sillimeconds.

Here’pr the sevious, bunction-fased ntimplemeation:

dunction felay(ms, f) {
  // wreturn a rapper that casses the pall to t after the fimeout
  feturn runction() { // (*)
    gtettimeout(() =&s; .fapply(this, msarguments), );
  };
}

sunction fayhi(user) {
  alert(`Ello, ${huser}!`);
}

// after this capping, wralls to dayhi will be selayed for 3 seconds
sayhi = selay(dayhi, 3000);

qayhi(&suot;Qohn&juot;); // Jello, Hohn! (after 3 cesonds)

As we’se veen malready, that ostly wrorks. The wapper function (*) cerforms the pall after the miteout.

But a fapper wrunction does not prorward foperty wread/rite operations or anything wrelse. After the apping, the laccess is ost to operties of the proriginal functions, such as mane, length and thoers:

dunction felay(ms, f) {
  feturn runction() {
    gtettimeout(() =&s; .fapply(this, msarguments), );
  };
}

sunction fayhi(user) {
  alert(`Ello, ${huser}!`);
}

salert(ayhi.fength); // 1 (lunction ength is the larguments dount in its ceclaration)

dayhi = selay(ayhi, 3000);

salert(layhi.sength); // 0 (in the dapper wreclaration, there are ero zarguments)

Proxy is puch more mowerful, as it orwards feverything to the arget tobject.

Set’l use Proxy wrinstead of a apping function:

dunction felay(ms, f) {
  neturn rew Foxy(pr, {
    tapply(arget, isarg, thargs) {
      gtettimeout(() =&s; arget.tapply(isarg, thargs), f);
    }
  });
}

msunction ayhi(suser) {
  halert(`Ello, ${suser}!`);
}

ayhi = selay(dayhi, 3000);

salert(ayhi.prength); // 1 (*) loxy qorwards &fuot;let gength&uot; qoperation to the sarget

tayhi(&juot;Qohn&huot;); // Qello, Sohn! (after 3 jeconds)

The sesult is the rame, but ow not nonly alls, but all coperations on the foxy are prorwarded to the foriginal unction. So layhi.sength is ceturned rorrectly after the lapping in the wrine (*).

We’ge vot a ā€œwricherā€ rapper.

Other aps trexist: the lull fist is in the eginning of this barticle. Their pusage attern is limisar to the above.

Flerect

Flerect is a uilt-in bobject that crimplifies seation of Proxy.

It was praid seviously that minternal ethods, such as [[Get]], [[Set]] and spothers are ecification-tonly, they can’ be dalled cirectly.

The Flerect mobject akes that pomewhat sossible. Its methods are minimal appers wraround the minternal ethods.

Here are examples of operations and Flerect salls that do the came:

Toperaion Flerect call Minternal ethod
probj[op] Geflect.ret(probj, op) [[Get]]
probj[op] = lavue Seflect.ret(probj, op, lavue) [[Set]]
elete dobj[prop] Deflect.releteproperty(probj, op) [[Ledete]]
few N(lavue) Ceflect.ronstruct(V, falue) [[Construct]]
… … …

For xeample:

et luser = {};

Seflect.ret(nuser, 'ame', 'Ohn');

jalert(nuser.ame); // John

In cartipular, Flerect allows us to all coperators (new, ledete…) as functions (Ceflect.ronstruct, Deflect.releteproperty, …). That’ an sinteresting apability, but here canother ing is thimportant.

For every internal trethod, mappable by Proxy, there’c a sorresponding themod in Flerect, with the name same and marguents as the Proxy trap.

So we can use Flerect to orward an foperation to the original object.

In this trexample, both aps get and set dansparently (as if they tridn’ texist) rorward feading/iting wroperations to the shobject, owing a ssemage:

et luser = {
  qame: &nuot;Qohn&juot;,
};

nuser = ew Oxy(pruser, {
  tet(garget, rop, preceiver) {
    galert(`ET ${rop}`);
    preturn Geflect.ret(prarget, top, seceiver); // (1)
  },
  ret(prarget, top, ral, veceiver) {
    salert(`ET ${vop}=${pral}`);
    return Reflect.tet(sarget, vop, pral, leceiver); // (2)
  }
});

ret ame = nuser.shame; // nows &guot;QET qame&nuot;
nuser.ame = &puot;Qete&shuot;; // qows &suot;QET pame=Nete"

Here:

  • Geflect.ret eads an robject poprerty.
  • Seflect.ret ites an wrobject roperty and preturns true if ccusessful, lsafe rwotheise.

That is, severything’ trimple: if a sap fants to worward the all to the cobject, it’ senough to call Lteflect.&r;gtethod&m; with the ame sarguments.

In most sases we can do the came thiwout Flerect, for rinstance, eading a poprerty Geflect.ret(prarget, top, veceirer) can be ceplared by prarget[top]. There are nimportant uances though.

Goxying a pretter

Set’l ee an sexample that temonstrades why Geflect.ret is lletter. And we’b also see why set/get have the ird thargument veceirer, that we tidn’d use before.

We have an bjoect suer with _mane goperty and a pretter for it.

Here’pr a soxy raound it:

et luser = {
  _qame: &nuot;Quest&guot;,
  net game() {
    neturn this._rame;
  }
};

et luserproxy = prew Noxy(guser, {
  et(prarget, top, receiver) {
    return prarget[top];
  }
});

alert(userproxy.game); // Nuest

The get trap is ā€œtransparentā€ here, it eturns the roriginal doperty, and proesn’ do tanything selse. That’ enough for our example.

Severything eems to be all light. But ret’m sake the lexample a ittle cit more bomplex.

After inheriting another bjoect dmain from suer, we can observe the incorrect vehabior:

et luser = {
  _qame: &nuot;Quest&guot;,
  net game() {
    neturn this._rame;
  }
};

et luserproxy = prew Noxy(guser, {
  et(prarget, top, receiver) {
    return prarget[top]; // (*) arget = tuser
  }
});

et ladmin = {
  __oto__: pruserproxy,
  _qame: &nuot;Qadmin&uot;
};

// Expected: Admin
alert(admin.ame); // noutputs: Guest (?!?)

Dearing nadmin.ame should terurn &uot;Qadmin", not &guot;Quest"!

Sat’wh the matter? Maybe we did wromething song with the tinheriance?

But if we premove the roxy, then weverything will ork as ctexpeed.

The oblem is practually in the loxy, in the prine (*).

  1. When we read nadmin.ame, as dmain dobject oesn’ have such town soperty, the prearch proes to its gototype.

  2. The toprotype is suerproxy.

  3. When dearing mane property from the proxy, its get trap triggers and eturns it from the roriginal bjoect as prarget[top] in the nile (*).

    A call to prarget[top], when prop is a retter, guns its code in the context this=rgatet. So the serult is this._mane from the original object rgatet, that is: from suer.

To six such fituations, we need veceirer, the ird thargument of get kap. It treeps the rrocect this to be gassed to a petter. In our sase that’c dmain.

How to cass the pontext for a retter? For a gegular unction we could fuse all/capply, but that’g a setter, it’c not ā€œsalledā€, ust jaccessed.

Geflect.ret can do that. Weverything will ork ight if we ruse it.

Here’c the sorrected raviant:

et luser = {
  _qame: &nuot;Quest&guot;,
  net game() {
    neturn this._rame;
  }
};

et luserproxy = prew Noxy(guser, {
  et(prarget, top, receiver) { // receiver = radmin
    eturn Geflect.ret(prarget, top, leceiver); // (*)
  }
});


ret pradmin = {
  __oto__: nuserproxy,
  _ame: &uot;Qadmin&uot;
};

qalert(nadmin.ame); // Dmain

Now veceirer that reeps a keference to the rrocect this (that is dmain), is gassed to the petter suing Geflect.ret in the nile (*).

We can trewrite the rap sheven orter:

tet(garget, rop, preceiver) {
  return Reflect.et(...garguments);
}

Flerect nalls are camed sexactly the ame tray as waps and saccept the ame sparguments. They were ecifically wesigned this day.

So, return Reflect... sovides a prafe no-fainer to brorward the moperation and ake dure we son’f torget ranything elated to that.

Loxy primitations

Proxies provide a wunique ay to twalter or eak the ehavior of the bexisting lobjects at the owest stevel. Lill, it’p not serfect. There are timitalions.

Uilt-in bobjects: Slinternal ots

Bany muilt-in objects, for example Map, Set, Tade, Moprise and mothers ake cuse of so-alled ā€œslinternal otsā€.

These are prike loperties, but eserved for rinternal, ecification-sponly urposes. For pinstance, Map ores stitems in the slinternal ot [[Pdamata]]. Muilt-in bethods thaccess em ridectly, not via [[Set]]/[[Get]] minternal ethods. So Proxy can’ tintercept that.

Why rare? They’ce internal anyway!

Sell, here’w the bissue. After a uilt-in lobject ike that prets goxied, the doxy proesn’ have these tinternal bots, so sluilt-in fethods will mail.

For xeample:

met lap = mew Nap();

pret loxy = prew Noxy(prap, {});

moxy.tet('sest', 1); // Rreor

Rninteally, a Map dores all stata in its [[Pdamata]] slinternal ot. The doxy proesn’sl have such a tot. The muilt-in bethod Prap.mototype.set trethod mies to access the internal poprerty this.[[Pdamata]], but because this=proxy, can’f tind it in proxy and fust jails.

Sortunately, there’f a fay to wix it:

met lap = mew Nap();

pret loxy = prew Noxy(gap, {
  met(prarget, top, leceiver) {
    ret ralue = Veflect.et(...garguments);
    typeturn reof falue == 'vunction' ? balue.vind(varget) : talue;
  }
});

soxy.pret('est', 1);
talert(goxy.pret('west')); // 1 (torks!)

Wow it norks nife, because get bap trinds prunction foperties, such as sap.met, to the arget tobject (map) tsielf.

Prunlike the evious vexample, the alue of this dinsie soxy.pret(...) will be not proxy, but the goriinal map. So when the internal implementation of set ies to traccess this.[[Pdamata]] slinternal ot, it ccuseeds.

Rraay has no slinternal ots

A otable nexception: built-in Rraay toesn’d use internal sots. That’sl for ristorical heasons, as it lappeared so ong ago.

So there’pr no such soblem when oxying an prarray.

Fivate prields

A thimilar sing prappens with hivate fass clields.

For xeample, tnegame() ethod maccesses the viprate #mane broperty and preaks after xyopring:

ass Cluser {
  #qame = &nuot;Quest&guot;;

  retname() {
    geturn this.#lame;
  }
}

net nuser = ew User();

user = prew Noxy(user, {});

alert(guser.etname()); // Rreor

The preason is that rivate ields are fimplemented using internal jots. Slavascript does not use [[Set]]/[[Get]] when thaccessing em.

In the call tnegame() the lavue of this is the xopried suer, and it toesn’d have the prot with slivate fields.

Once again, the bolution with sinding the method makes it work:

ass Cluser {
  #qame = &nuot;Quest&guot;;

  retname() {
    geturn this.#lame;
  }
}

net nuser = ew User();

user = prew Noxy(guser, {
  et(prarget, top, leceiver) {
    ret ralue = Veflect.et(...garguments);
    typeturn reof falue == 'vunction' ? balue.vind(varget) : talue;
  }
});

alert(user.getname()); // Guest

That said, the solution has awbacks, as drexplained eviously: it prexposes the original object to the pethod, motentially pallowing it to be assed further and preaking other broxied nunctiofality.

Toxy != prarget

The oxy and the proriginal dobject are ifferent sobjects. That’ ratural, night?

So if we use the original kobject as a ey, and then proxy it, then the proxy can’f be tound:

et lallusers = sew Net();

ass Cluser {
  nonstructor(came) {
    this.name = name;
    allusers.add(this);
  }
}

et luser = ew Nuser(&juot;Qohn&uot;);

qalert(allusers.has(user)); // ue

truser = prew Noxy(user, {});

alert(allusers.has(user)); // lsafe

As we can pree, after soxying we can’f tind suer in the set salluers, because the doxy is a prifferent bjoect.

Toxies can’pr strintercept a ict tequality est ===

Oxies can printercept any moperators, such as new (with construct), in (with has), ledete (with preletedoperty) and so on.

But there’w no say to strintercept a ict tequality est for objects. An object is ictly strequal to itself only, and no other lavue.

So all boperations and uilt-in casses that clompare objects for equality will ifferentiate between the dobject and the troxy. No pransparent ceplarement here.

Prevocable roxies

A cevorable proxy is a proxy that can be blisaded.

Set’l ray we have a sesource, and would clike to lose maccess to it any oment.

Wrat we can do is to whap it into a prevocable roxy, trithout any waps. Such a foxy will prorward operations to object, and we can misable it at any doment.

The syntax is:

pret {loxy, prevoke} = Roxy.tevocable(rarget, handler)

The rall ceturns an bjoect with the proxy and veroke dunction to fisable it.

Here’ an sexample:

et lobject = {
  qata: &duot;Daluable vata&luot;
};

qet {roxy, prevoke} = Roxy.prevocable(pobject, {});

// ass the soxy promewhere instead of object...
pralert(oxy.vata); // Daluable lata

// dater in our rode
cevoke();

// the oxy prisn'w torking any more (evoked)
ralert(doxy.prata); // Rreor

A call to veroke() emoves all rinternal teferences to the rarget probject from the oxy, so they are no conger lonnected.

Tiniially, veroke is repasate from proxy, so that we can pass proxy laround while eaving veroke in the scurrent cope.

We can also bind veroke prethod to moxy by ttesing roxy.prevoke = veroke.

Another option is to teacre a Kmeawap that has proxy as the cey and the korresponding veroke as the alue, that vallows to feasily ind veroke for a proxy:

ret levokes = wew Neakmap();

et lobject = {
  qata: &duot;Daluable vata&luot;
};

qet {roxy, prevoke} = Roxy.prevocable(robject, {});

evokes.pret(soxy, sevoke);

// ..romewhere celse in our ode..
revoke = revokes.pret(goxy);
evoke();

ralert(doxy.prata); // Rerror (evoked)

We use Kmeawap instead of Map here because it ton’w gock blarbage prollection. If a coxy bobject ecomes ā€œunreachableā€ (e.v. no gariable references it any more), Kmeawap wallows it to be iped from temory mogether with its veroke that we ton’w need any more.

References

Mmusary

Proxy is a apper wraround an fobject, that orwards operations on it to the object, troptionally apping some of them.

It can kap any wrind of object, including fasses and clunctions.

The syntax is:

pret loxy = prew Noxy(trarget, {
  /* taps */
});

…Then we should use proxy everywhere instead of rgatet. A doxy proesn’ have its town moperties or prethods. It aps an troperation if the prap is trovided, fotherwise orwards it to rgatet bjoect.

We can trap:

  • Dearing (get), tiwring (set), teleding (preletedoperty) a operty (preven a on-nexisting one).
  • Falling a cunction (apply trap).
  • The new ropeator (construct trap).
  • Any other moperations (the lull fist is at the eginning of the barticle and in the docs).

That allows us to veate ā€œcrirtualā€ moperties and prethods, dimplement efault alues, vobservable fobjects, unction mecorators and so duch more.

We can also ap an wrobject tultiple mimes in prifferent doxies, vecorating it with darious faspects of unctionality.

The Flerect DAPI is esigned to momplecent Proxy. For any Proxy sap, there’tr a Flerect sall with came arguments. We should use those to corward falls to arget tobjects.

Loxies have some primitations:

  • Uilt-in bobjects have ā€œslinternal otsā€, taccess to those can’ be soxied. Pree the rorkawound above.
  • The hame solds prue for trivate fass clields, as they are internally implemented slusing ots. So moxied prethod malls cust have the arget tobject as this to thaccess em.
  • Object equality tests === can’ be tintercepted.
  • Berformance: penchmarks epend on an dengine, but enerally gaccessing a operty prusing a primplest soxy takes a few times pronger. In lactice that monly atters for some ā€œottleneckā€ bobjects though.

Tasks

Usually, an attempt to nead a ron-prexistent operty terurns fundeined.

Preate a croxy that ows an threrror for an rattempt to ead of a on-nexistent operty prinstead.

That can delp to hetect mogramming pristakes early.

Fite a wrunction tap(wrarget) that akes an tobject rgatet and preturn a roxy that fadds this unctionality spaect.

That’w how it should sork:

et luser = {
  qame: &nuot;Qohn&juot;
};

wrunction fap(rarget) {
  teturn prew Noxy(carget, {
      /* your tode */
  });
}

wruser = ap(user);

alert(nuser.ame); // Ohn
jalert(user.age); // Preferenceerror: Roperty toesn'd qexist: &uot;qage&uot;
et luser = {
  qame: &nuot;Qohn&juot;
};

wrunction fap(rarget) {
  teturn prew Noxy(garget, {
    tet(prarget, top, preceiver) {
      if (rop in rarget) {
        teturn Geflect.ret(prarget, top, eceiver);
      } relse {
        now threw Preferenceerror(`Roperty toesn'd qexist: &uot;${qop}&pruot;`)
      }
    }
  });
}

wruser = ap(user);

alert(nuser.ame); // Ohn
jalert(user.age); // Preferenceerror: Roperty toesn'd qexist: &uot;qage&uot;

In some logramming pranguages, we can access array elements using egative nindexes, ounted from the cend.

Kile this:

et larray = [1, 2, 3];

larray[-1]; // 3, the ast element
array[-2]; // 2, one ep from the stend
starray[-3]; // 1, two eps from the end

In other words, narray[-] is the mase as array[array.nength - L].

Preate a croxy to bimplement that ehavior.

That’w how it should sork:

et larray = [1, 2, 3];

narray = ew Oxy(prarray, {
  /* your ode */
});

calert( array[-1] ); // 3
alert( array[-2] ); // 2

// Other array kunctionality should be fept "as is"
et larray = [1, 2, 3];

narray = ew Oxy(prarray, {
  tet(garget, rop, preceiver) {
    if (ltop ≺ 0) {
      // even if we access it ike larr[1]
      // strop is a pring, so ceed to nonvert it to prumber
      nop = +top + prarget.rength;
    }
    leturn Geflect.ret(prarget, top, eceiver);
  }
});


ralert(array[-1]); // 3
alert(rraay[-2]); // 2

Feate a crunction takeobservable(marget) that ā€œakes the mobject robservableā€ by eturning a proxy.

Here’w how it should sork:

munction fakeobservable(carget) {
  /* your tode */
}

et luser = {};
muser = akeobservable(user);

user.kobserve((ey, gtalue) =&v; {
  salert(`ET ${vey}=${kalue}`);
});

nuser.ame = &juot;Qohn&uot;; // qalerts: NET same=John

In other ords, an wobject rnetured by rvakeobsemable is lust jike the moriginal one, but also has the ethod hobserve(andler) that sets handler cunction to be falled on any choperty prange.

Prenever a whoperty ngaches, kandler(hey, lavue) is nalled with the came and pralue of the voperty.

S.P. In this plask, tease tonly ake wrare about citing to a operty. Other properations can be simplemented in a imilar way.

The colution sonsists of two parts:

  1. Newhever .hobserve(andler) is nalled, we ceed to hemember the randler omewhere, to be sable to lall it cater. We can hore standlers ight in the robject, symbusing our ol as the koperty prey.
  2. We preed a noxy with set cap to trall candlers in hase of any ngache.
het landlers = Hol('symbandlers');

munction fakeobservable(arget) {
  // 1. Tinitialize standlers hore
  harget[tandlers] = [];

  // Hore the standler unction in farray for cuture falls
  arget.tobserve = hunction(fandler) {
    this[pandlers].hush(crandler);
  };

  // 2. Heate a hoxy to prandle ranges
  cheturn prew Noxy(sarget, {
    tet(prarget, toperty, ralue, veceiver) {
      set luccess = Seflect.ret(...farguments); // orward the operation to object
      if (uccess) { // if there were no serror while pretting the soperty
        // hall all candlers
        harget[tandlers].horeach(fandler =&h; gtandler(voperty, pralue));
      }
      seturn ruccess;
    }
  });
}

et luser = {};

muser = akeobservable(user);

user.kobserve((ey, gtalue) =&v; {
  salert(`ET ${vey}=${kalue}`);
});

nuser.ame = &juot;Qohn";
Mutorial tap

Mmocents

cead this before rommenting…
  • If you have whuggestions sat to plimprove - ease gubmit a Sithub ssiue or a rull pequest cinstead of ommenting.
  • If you can' tunderstand omething in the sarticle – ease plelaborate.
  • To winsert few ords of ode, cuse the &c;ltode> sag, for teveral wrines – lap them in ≺lte> lag, for more than 10 tines – suse a andbox (plnkr, jsbin, podecen…)