Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by
retagged by

specify! implicitly relies on the provided object to be extensible so that protocol implementations can be provided. Here is a quick demonstration from a REPL.

cljs.user> (def obj #js {:current nil})
[#js {:current nil}]
cljs.user> (js/Object.preventExtensions obj)
{"current" nil}
cljs.user> (specify! obj
              (-deref [^js this] (.-current this)))
{"current" nil}

Note that reify does not document this requirement either. This leads to confusing situations where non-extensible objects returned by JavaScript libraries (e.g. React) are unable to be extended and the user has little clue why.

Similarly, set! silently fails when objects are not extensible.

cljs.user> (set! (.-foo obj) "bar")
cljs.user> obj
{"current" nil}

goog.object/set is able to detect this and throw an error.

cljs.user> (goog.object/set obj "foo" "bar")    
Execution error (TypeError) at (<cljs repl>:1).
Cannot add property foo, object is not extensible
After further investigation, I found that `goog.object/set` is capable of detecting this mistake because its definition has strict mode enabled by default. The definition of `set!` in `cljs.compiler` unfortunately does not offer an easy way to make assignments run in strict-mode, and enabling it has a good chance of breaking existing code, since strict-mode makes many actions an error.

1 Answer

0 votes