IMHO Clojurescript is somewhat lacking when it comes to official documentation, hence this blog post, and the need to quote from twitter:
I’m going to look into the tradeoffs of what David and Mike say there.
Firstly, I’ll try to make a clear distinction between JS data vs API: A data object is any object you could round-trip through JSON/stringify => JSON/parse. An object that may appear to be a data object because it only contains properties (ie no methods), may not be a data object, because those properties might be getters or setters. If the object came via a JS library, it’s most likely not a data object unless the library documentation explicitly says so.
That definition should be good enough for the purposes of this post, let’s ignore prototypes etc for now.
I find the
(.-length "foo") item in Mike’s list interesting.
It’s clear in the case of the string
"foo" that this is not a data object
goog.object/get will not work to access
length, or any other property of a string.
But consider this example though:
(goog.object/get #js "length")
0, so demonstrating it is possible to use the
goog.object API to access API properties.
So, we have what appears to be just a stylistic choice between that and
(.-length #js). Why choose either one?
It’s easy to see that the goog.object one will survive advanced compilation (meaning it is compiled to
.length or equivalent), whereas in some
cases the dot-access might need a type hint (as in
(.-length ^js foo)) to avoid
length being renamed.
So +1 for the goog.obj approach so far I guess.
In working with the API of some JS object, it’s quite common to both access properties and call methods:
(let [foo ^js (some-fn) bar-prop (.-bar foo)] (.methodFoo foo (inc bar-prop)))
We could have accessed
this example is being consistent in using dot access only for the API of
foo. We could also have accessed
goog.object/get (and then invoked it), but I don’t think it would be idiomatic to do so.
So, Following a rule ‘dot access only for APIs’ means the code makes a clear statement that it is
working with API, not data - regardless of whether it is methods or properties we need to
use. This comes as the cost of having to remember to put type hints in. I’ve come to think
type hints aren’t so bad, because now type hints are documented
I think it’s easy enough to understand you just need to add
^js when you first see the js object in scope.
Yet another approach would be to use a library such as js-interop. Using that,
you would write
(j/call foo :bar 1) for the js equivalent
foo.bar(1). Type hints are not needed if you use that
library, so it’s definitely an alternative to consider.
If working with JS data, ie not API, then an alternative to
goog.object is Cljs Bean
So, now that’s all cleared up, which dot-access is preferred,
(.. a -b -c) …?
Dot access in the style of
a.b.c does have an issue,
which is a shame (until fixed) because to me that seems pretty tasteful.
Thomas Heller pointed out to me that if Google Closure did become able to optimise regular JS libraries (things that are now foreign)
at some point in the future, then anything that is using strings to access JS APIs (
goog.get & etc) would then be broken,
so that’s something else to consider