The Proxies API is a low-level catch-all thing that you can wrap around your objects to implement almost anything you want within the syntactic limitations of JavaScript. Firefox supports it, and so does the latest version of V8 apparently.
The existential operator (the accessor kind), if it existed in JavaScript, would allow us attempted access of nested properties in JavaScript without ever getting a TypeError. Here’s what usually happens:
var obj = { foo: 123 }; obj.foo.bar.bob; // Throws TypeError (obj.foo.bar is undefined) |
CoffeeScript implements an existential operator that allows you to attempt access at a property without throwing errors. It will fail quietly by returning a harmless undefined
value. In CoffeeScript, it looks like this:
obj?.foo?.bar?.bob // returns undefined |
We can’t emulate this entirely but we can make a decent alternative using the Proxies API. In this example we’re using the dollar sign to indicate a existential-property-accessor-operator:
obj = {}; obj.foo$bar$bob; // => undefined obj = { foo: { bar: { bob: 321 } } }; obj = existentially(obj); // wrap it in magic obj.foo$bar$bob; // => 321 |
No, it’s not as cool and no, you probably shouldn’t use it in production. Yes, it’ll fail if obj
doesn’t exist. Anyway, I thought it was pretty neat. Here’s how it’s done:
function existentially(obj) { if (obj == null) return obj; var proxy = handlerMaker(obj); proxy.get = function(_, name) { name = name.split('$'); var found = obj; while ( name[0] && null != (found = found[name.shift()]) ); return found; }; proxy.set = function(_, name, val) { name = name.split('$'); var found = obj; while ( name[1] && null != (found = found[name.shift()]) ); if (found) { found[name] = val; } return true; }; return Proxy.create(proxy); } // fyi // a == null // is the same as // a === undefined || a === null |
The referenced handlerMaker
simply defines all fundamental traps. You can grab it from the MDN Proxy API page.
You’ll notice that we’re also defining a get
trap, which enables stuff like:
var foo = existentially(document.getElementById('foo')); if (foo) { // yes, we still need the initial null check foo.firstChild$style$display = 'none'; // won't throw an error, even if `firstChild` doesn't exist } |
Okay, granted, the dollar sign doesn’t suit well and we’re just pandering to what’s allowed in JS, but it’s still a good illustration of what’s possible with the Proxies API. You might want to use another fake operator — possibly something that isn’t likely to be found as part of a property name itself… something like _$_
would be okay, except that it’s ugly and, just like $
, carries no semantic value whatsoever.
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
This piece of code:
var bob = obj?.foo?.bar?.bob
Could be written in javascript without any proxies or other workarounds, natively. It’s just a little bit more verbose, but it’s stil one line of code:
var bob = (((obj || {}).foo || {}).bar || {}).bob;
Cool huh 🙂
I guess it comes to where you draw the line between the value of a language construct, versus just using a regular function. I have a function that parses a string to determine existence just like this (which I actually use most often to call functions based on a string which is passed from an external data source, as in a route), like:
var bob = route(“obj.foo.bar.bob”); // returns null if nonexistent
It’s not as syntactically beautiful as “bob = obj.foo.bar.bob” would be, but is that bad? It’s easy enough to understand; it expresses a different intent than a native evaluation anyway (since it’s parameter is indeterminate); and since javascript is interpreted at run-time anyway, there’s no downside in terms of syntax checking as there might be in a compiled language. Though since the whole point here is that we don’t know if the operand exists, syntax checking doesn’t make much sense anyway. In that way I kind of think it’s more correct to keep it as a string in your code.
http://blog.ramonlechuga.com/2010/10/20/checking-object-structure/
Cool ! stuff bookmarked
”mixIn” describes that properties are being copied from one object to another, other possible names: “aggregate”, “combine”, “merge”.
var foo = existentially(document.getElementById(‘foo’));
if (foo) { // yes, we still need the initial null check
foo.//firstChild$style$display// = ‘none’;
// won’t throw an error, even if `firstChild` doesn’t exist
}