ここ数日ClojureScript界隈でオブジェクトに対してaget/asetを使うな!
という話題が散見されており、何故そうなのかを調べてみた。
要するに?
- aget/asetは元々配列に対する操作であり、オブジェクトに対しても動作するのは偶然
- jvmの方のClojureではこのような使い方は出来ない
- Closure Libraryにオブジェクトを操作するための関数があるのでそちらを利用するべき
- goog.object/get
- goog.object/set
- aget/asetでオブジェクトを操作しようとすると警告が出るようにするオプションがコンパイラに追加された
- aget/asetが動かなくなるわけでは無い
- パフォーマンスクリティカルなケースに対応するための 'unsafe-get' マクロが追加された
オブジェクトのプロパティへのアクセス方法
.-でアクセス
扱うオブジェクトが':optimizations :advanced'(いわゆるアドバンスドコンパイル)の際に、
リネームの対象になる場合は以下の用にしてプロパティにアクセスする。
.-propertyはアドバンスコンパイルの際に適切にリネームされる。
(.-property an-object)
aget
一方、扱うオブジェクトが外部ライブラリ等でアクセスするキーが予め定まっている場合、
'.-' でプロパティにアクセスを行うとアドバンスコンパイル時に意図していたキーとは異なる値にリネームされてしまう。
このようなケースにexternファイルをわざわざ書きたくない人は多数おり、
- Closure Compilerが文字列には一切手をつけないこと
- JavaScriptではオブジェクトに対して文字列の添字でアクセスできること
を逆手にとりagetを利用して回避していた。
事実
(aget an-object "property")
は
(cljs.user.an-object["property"])
のようにコンパイルされ、動く。
goog.object
agetの代わりに利用を推奨されているgoog.object/getは以下のように使う
(require '[goog.object :as o])
(o/get #js {:foo "bar} "foo")
=> "bar"
参考
- https://google.github.io/closure-library/api/goog.object.html
- https://github.com/clojure/clojurescript/commit/fb6c04f4ae16877ba7fa797e9378d8a1ba74fd89
- https://github.com/clojure/clojurescript/commit/3b5d88a2546c07fc27da5014445f03ac15a9f22f
- https://dev.clojure.org/jira/browse/CLJS-2148
- https://twitter.com/mfikes/status/881315944257204224
- https://github.com/glittershark/core-async-storage/pull/8