Two Ways to Access Properties in ClojureScript

There are two pairs of complementary functions to set properties on objects in ClojureScript. One is aset and aget, another is set! and .-propname:

(def scope (js-obj))
(aset scope "var1" "Value")
(aget scope "var1")
(def scope (js-obj))
(set! (.-var2 scope) "Value")
(.-var2 scope)

Are they equivalent? Is syntax the only difference?

For a demo, let’s say we set and print two fields on an object, like this:

(def scope (js-obj))
(aset scope "var1" "Value")
(set! (.-var2 scope) "Value")

(.log js/console (str "(aget scope \"var1\") ->" (aget scope "var1")))
(.log js/console (str "(.-var2 scope) ->" (.-var2 scope)))
(.log js/console (str "(.-var1 scope) ->" (.-var1 scope)))
(.log js/console (str "(aget scope \"var2\") ->" (aget scope "var2")))

The resulting “setting” instructions are deceptively similar:

mynamespace.scope = {};
mynamespace.scope["var1"] = "Value";
mynamespace.scope.var2 = "Value";

And of course all combinations print out correctly – I can see this on the console:

(aget scope "var1") ->Value
(.-var2 scope) ->Value
(.-var1 scope) ->Value
(aget scope "var2") ->Value

So far so good.

Now, if you’re at least a little bit serious about using ClojureScript, you have to consider advanced optimizations. What happens in this mode?

My code got compiled to:

var Xm;
Xm = {var1:"Value", lc:"Value"};

And on the console all I see is:

(aget scope "var1") ->Value
(.-var2 scope) ->Value
(.-var1 scope) ->
(aget scope "var2") ->

What happened to var2? Why is set! suddenly incompatible with aget and aset with .-field?

If you look at compiler output in both cases, you should see that in one case the value is bound by name and in the other by symbol. In my understanding, it has a few consequences:

  • Advanced compiler renames symbols, but leaves strings intact.
  • aset and aget operate on names. set! and .-var operate on symbols. If you set a variable using “symbolic reference”, the compiler will rename the symbol so you won’t be able to get it by name anymore. If you set it by name with aset, reading by symbol as .-var will not work either because the symbol is renamed.
  • If you deal only with ClojureScript, you should probably use the symbolic references. They look more idiomatic and will benefit from advanced compiler features such as minimization. On the other hand, in this case you often may want to leverage the idiomatic data bindings – vars, atoms and such.
  • As soon as you want your names to be accessible from outside, or you want to access names set outside, use only aset/aget. It obviously applies to interop with JavaScript libraries. Less obvious case (for me) was libraries that operate on DOM, like Angular or Knockout. If you want that {{user.name}} to work, you cannot use the symbolic version.

It all makes sense now, but I haven’t seen it documented. Well, that’s still true for most ClojureScript.

2 thoughts on “Two Ways to Access Properties in ClojureScript

  1. One more thing aget supports nested objects.

    Like this

    (aget #js {“name” #js {“firstname” “geraldo” “surname” “lopes”}} “name” “surname”)

    result: “lopes”

Leave a Reply

Your email address will not be published. Required fields are marked *

Spam protection by WP Captcha-Free