A few weeks back, I wrote a post, which discussed Groovy’s safe dereferencing operator (?.) and provided C# code snippets to demonstrate comparable functionality. I’ve also previously highlighted the Elastic Object, a compelling use case for C#’s dynamic keyword. The Elastic Object illustrates the potential of integrating custom logic into the method or property lookup process. C#’s dynamic achieves this through TryGetMember, TryInvokeMember, while Groovy utilizes invokeMethod and getProperty. Similarly, having a mechanism to handle missing methods or properties proves quite valuable. Groovy accomplishes this with MethodMissing and PropertyMissing.
Given that JavaScript is another favorite language of mine, a natural question arises: can we replicate this in JavaScript?
In short, the answer is no. Regrettably, JavaScript (at least EcmaScript 5) lacks the robust capabilities of features like invokeMethod or TryInvokeMember. Although ES6’s introduction of proxies seems like a step in the right direction, I’m uncertain about its full extent (I haven’t had the opportunity to delve deeply into them). Currently, we can achieve partial functionality:
As detailed in this previous article, we can utilize JavaScript’s accessor properties, functions as objects, closures, and other mechanisms to intercept access to existing properties or method invocations.
Firefox offers the non-standard __noSuchMethod__ hook to capture invocations of non-existent methods.
This leaves us with a few gaps: intercepting access to missing properties and handling invocations of missing methods (except in Firefox). Furthermore, while we could employ something like my intercept.js library to add interceptors to all methods and properties of an object at a specific moment, any subsequent additions of methods or properties to that object won’t inherit those interceptors. There’s no notification mechanism for object expansion, and we can only seal it to prevent modifications.
After that detour, let’s return to the post’s main point:
For Groovy’s Safe Dereferencing operator, I’ve created a safeGet function. It accepts an object and a string representing a chain of properties, enabling safe access to nested properties. If the final property in the chain is reachable, it returns its value; otherwise, it returns undefined.
functionsafeGet(obj,propertiesStr){if(!obj||!propertiesStr)returnundefined;varproperties=propertiesStr.split(".");varcurObj=obj;while(curObj&&properties.length){varcurProp=properties.shift();curObj=curObj\[curProp\];}returncurObj;}varperson={name:"xuan",country:{name:"Asturies",capital:{name:"Uviéu",population:1100000}}};/\*//thiscodeisugly...if(person.country&&person.country.capital&&person.country.capital.name)console.log(person.country.capital.name);elseconsole.log("undefined"); \*/console.log("the capital of my country is: "+safeGet(person,"country.capital.name"));console.log("the population of my country is: "+safeGet(person,"country.population"));
Inspired by the “Elastic Object,” I’ve also implemented a safeSet function. It takes an object, a property chain string, and a value. This function sets the final property in the chain to the given value, creating empty objects for any missing properties along the way.
functionsafeSet(obj,propertiesStr,value){if(!obj||!propertiesStr)return;varproperties=propertiesStr.split(".");varcurObj=obj;for(vari=0;i≶properties.length-1;i++){if(!curObj\[properties\[i\]\]){curObj\[properties\[i\]\]={};}curObj=curObj\[properties\[i\]\];}curObj\[properties\[i\]\]=value;}varp={};/\*//thiscodeisugly...p.country=p.country||{};p.country.capital=p.country.capital||{};p.country.capital.name="Uviéu"; \*/safeSet(p,"country.capital.name","Uviéu");console.log("the capital of my country is: "+p.country.capital.name);safeSet(p,"country.capital.name","Berlín");console.log("the capital of my country is: "+p.country.capital.name);
Using these two functions, I’ve crafted my own nameSpace function. Note that this is primarily beneficial in browser environments. Namespaces seem less crucial in settings with the CommonJS module system (like Node.js).
varnameSpace=(function \_nameSpaceFactory(){varroot;if(typeofwindow=="object"){root=window;}//wellGLOBALworksonlyatthemodulelevel,andinnodewiththeCommonJSmodulesystem,allthisnamespacingthingisnotreallynecessaryelseif(isNode()){root=GLOBAL;}elseconsole.log("we don't know the global object for this environment, namespace function won't work");returnfunction \_nameSpace(nameSpaceStr){if(!safeGet(root,nameSpaceStr))safeSet(root,nameSpaceStr,{});};}());//createsthenamespacenameSpace("org.deployToNenyures.base");org.deployToNenyures.base.version="0.0.1";console.log(org.deployToNenyures.base.version);//namespacealreadyexists,sodoesnothingnameSpace("org.deployToNenyures.base");console.log(org.deployToNenyures.base.version);```**Update,2013/01/27**Youcanfindthesourcecode[here](http://www.telecable.es/personales/covam1/deployToNenyures/SourceCode/safeGetSet/safeGetSet.js)andtryitouteitherinamodernbrowser[here](http://www.telecable.es/personales/covam1/deployToNenyures/SourceCode/safeGetSet/safeGetSetTest.html)orusingNode.js[here](http://www.telecable.es/personales/covam1/deployToNenyures/SourceCode/safeGetSet/test1.js)Groovy's safe reference (or safe navigation) extends to method invocation, returning null if a method is absent. This resonated with me, leading me to introduce a safeInvoke function. Consistent with my other functions, it returns _undefined_ if the method doesn'texist.Additionally,I've decided to [upload it](https://github.com/XoseLluis/safeNavigation.js) this to my GitHub account.