You have a number of options here depending on how much performance you need and what you're willing to give up. I put some benchmarks up on GitHub if you're interested.
By using records and native field access, you can cut the runtime for your original ClojureScript solution in half:
(defrecord XY [x y])
(def data (mapv (fn [_] (XY. (rand) (rand))) (range NUM)))
(defn sumXsAndYsWithLoopAndNativeFieldAccess [data]
(loop [x 0 y 0 data data]
(if (seq data)
(let [o (first data)]
(recur (+ x (.-x o)) (+ y (.-y o)) (rest data)))
[x y])))
(time (sumXsAndYsWithLoopAndNativeFieldAccess data))
You can also use arrays as mutable locals and get a solution only 8 times as slow as the native JavaScript version:
(defn sumsXsAndYsWithDotimesNativeFieldAccessAndMutableLocals [data]
(let [x (doto (make-array 1)
(aset 0 0))
y (doto (make-array 1)
(aset 0 0))]
(dotimes [i (count data)]
(let [o (data i)]
(aset x 0 (+ (aget x 0) (.-x o)))
(aset y 0 (+ (aget y 0) (.-y o)))))
[(aget x 0) (aget y 0)]))
(time (sumsXsAndYsWithDotimesNativeFieldAccessAndMutableLocals data))
Further, you can use the above in conjunction with arrays and achieve a solution approximately 3 times as slow as the native JavaScript version:
(def data (into-array (mapv #(XY. (rand) (rand)) (range NUM))))
(defn sumsXsAndYsWithDotimesOnArrayNativeFieldAccessAndMutableLocals [data]
(let [x (doto (make-array 1)
(aset 0 0))
y (doto (make-array 1)
(aset 0 0))]
(dotimes [i (alength data)]
(let [o (aget data i)]
(aset x 0 (+ (aget x 0) (.-x o)))
(aset y 0 (+ (aget y 0) (.-y o)))))
[(aget x 0) (aget y 0)]))
(time (sumsXsAndYsWithDotimesOnArrayNativeFieldAccessAndMutableLocals data))
You might want to check out David Nolen's Chambered project. He has some nice macros for creating and updating local mutables that make the above not look ridiculous.
Anyways, hope that helps.