-
Notifications
You must be signed in to change notification settings - Fork 17
Notes
I have tried to tune the macros so that they emit code (via :advanced compilation) that's similar to what you might write by hand in javascript, so that you can use this even for relatively performance-sensitive code.
For very performance-sensitive contexts, you can forego null-safety by using "unchecked" operations, which will throw errors if passed nil. Two are available in js-interop: j/unchecked-get and j/unchecked-set, used as follows:
(j/unchecked-get o :someKey) ;; returns `o`
(j/unchecked-get o .-someKey) ;; works with renamable keys
(j/unchecked-set o :a 10 :b 20) ;; can set multiple properties at once
(j/unchecked-set o .-a 10 .-b 20)
These are not recommended unless you really need them.
The general rule is that you should use "renamable" keys (ie. use dot-syntax, like .-someKey) for code that is run through the Closure compiler, which usually means ClojureScript code.
Objects created using js literal syntax, like #js:{:hello "world}, do not have renamable keys, so you should use string/keyword keys to access them. Similarly, objects created from parsing JSON, for example data that you fetch from an API, are also not renamable.
(-> (fetch-post ...)
(.then (fn [post] (j/get post :title))))
It's possible to implement ILookup for "all" JavaScript objects, as explained by Mike Fikes here. This will only have an effect on plain objects whose prototype is js/Object, not for any other kind of object (eg. it won't work for browser events or React components). It also mutates the global js/Object.
How it's done:
Associative destructuring is based on get, which can be mapped onto goog.object/get for JavaScript objects by extending them to ILookup:
(extend-type object ILookup (-lookup ([o k] (goog.object/get o (name k))) ([o k not-found] (goog.object/get o (name k) not-found))))
One thing js-interop does that is simply not possible using other tools is provide "lookup" semantics for host-interop (dot) keys. Using this feature looks like this: (j/get o :someKey :not-found). Following Clojure's lookup semantics means that we must return the :not-found value when :someKey is not contained by the object, regardless of what value :someKey may have, even if it is nil. The presence of a key is distinguished from its value.
Implementing this requires using the Closure Compiler reflection tool goog.reflect/objectProperty. At compile time, it can tell us what a given key will be renamed to. An expression like (.-someProperty x) is going to compile to something like x.Gx. In this case, the reflect/objectProperty function would tell us that someProperty => Gx.