Skip to content

Commit 1928849

Browse files
authored
cljs.proxy (#286)
- add `cljs.proxy` namespace, provides a single function `builder` which can take optional `key-fn`. can proxy maps as Objects, and Vector as array-like.
1 parent 54d013d commit 1928849

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed

src/main/cljs/cljs/proxy.cljs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
(ns cljs.proxy
2+
(:refer-global :only [Proxy isNaN])
3+
(:require [cljs.proxy.impl :refer [SimpleCache]]))
4+
5+
(defn- write-through [f]
6+
(let [cache (SimpleCache. #js {} 0)]
7+
(fn [x]
8+
(let [v (.get cache x)]
9+
(if (some? v)
10+
v
11+
(.set cache x (f x)))))))
12+
13+
(def ^{:private true}
14+
desc
15+
#js {:configurable true
16+
:enumerable true})
17+
18+
(defn builder
19+
"EXPERIMENTAL: Return a JavaScript Proxy ctor fn with the provided key-fn. You
20+
can proxy ClojureScript map and vectors. Access pattern from JavaScript
21+
will lazily wrap collection values in Proxy if needed. Note key-fn
22+
is only used for proxied ClojureScript maps. This function should map
23+
strings to the appropriate key representation. All maps proxied from the
24+
same ctor fn will share the same key-fn cache."
25+
([]
26+
(builder keyword))
27+
([key-fn]
28+
(js* "var __ctor")
29+
(let [cache-key-fn (write-through key-fn)
30+
vec-handler #js {:get (fn [^cljs.core/IIndexed target prop receiver]
31+
(if (identical? prop "length")
32+
(-count ^cljs.core/ICounted target)
33+
(let [n (js* "+~{}" prop)]
34+
(when (and (number? n)
35+
(not (isNaN n)))
36+
(js/__ctor (-nth target n nil))))))
37+
38+
:has (fn [^cljs.core/IAssociative target prop]
39+
(if (identical? prop "length")
40+
true
41+
(let [n (js* "+~{}" prop)]
42+
(and (number? n)
43+
(not (isNaN n))
44+
(<= 0 n)
45+
(< n (-count ^cljs.core/ICounted target))))))
46+
47+
:getPrototypeOf
48+
(fn [target] nil)
49+
50+
:ownKeys
51+
(fn [target] #js ["length"])
52+
53+
:getOwnPropertyDescriptor
54+
(fn [target prop] desc)}
55+
map-handler #js {:get (fn [^cljs.core/ILookup target prop receiver]
56+
(js/__ctor (-lookup target (cache-key-fn prop))))
57+
58+
:has (fn [^cljs.core/IAssociative target prop]
59+
(-contains-key? target (cache-key-fn prop)))
60+
61+
:getPrototypeOf
62+
(fn [target] nil)
63+
64+
:ownKeys
65+
(fn [target]
66+
(when (nil? (.-cljs$cachedOwnKeys target))
67+
(set! (. target -cljs$cachedOwnKeys)
68+
(into-array (map -name (keys target)))))
69+
(.-cljs$cachedOwnKeys target))
70+
71+
:getOwnPropertyDescriptor
72+
(fn [target prop] desc)}
73+
__ctor (fn [target]
74+
(cond
75+
(implements? IMap target) (Proxy. target map-handler)
76+
(implements? IVector target) (Proxy. target vec-handler)
77+
:else target))]
78+
__ctor)))
79+
80+
(comment
81+
82+
(def c (SimpleCache. #js {} 0))
83+
(.set c "foo" :foo)
84+
(.get c "foo")
85+
(.-cnt c)
86+
(.clear c)
87+
(.get c "foo")
88+
89+
(def kw (write-through keyword))
90+
(kw "foo")
91+
92+
(time
93+
(dotimes [i 1e6]
94+
(kw "foo")))
95+
96+
(time
97+
(dotimes [i 1e6]
98+
(keyword "foo")))
99+
100+
(def proxy (builder))
101+
102+
(def raw-map {:foo 1 :bar 2})
103+
(def proxied-map (proxy {:foo 1 :bar 2}))
104+
105+
(require '[goog.object :as gobj])
106+
(gobj/get proxied-map "foo")
107+
(gobj/get proxied-map "bar")
108+
(gobj/getKeys proxied-map)
109+
(.keys js/Object proxied-map)
110+
111+
(time
112+
(dotimes [i 1e7]
113+
(unchecked-get proxied-map "foo")))
114+
115+
(def k :foo)
116+
(time
117+
(dotimes [i 1e7]
118+
(get raw-map k)))
119+
120+
(def proxied-vec (proxy [1 2 3 4]))
121+
(alength proxied-vec)
122+
(time
123+
(dotimes [i 1e6]
124+
(alength proxied-vec)))
125+
126+
(nth [1 2 3 4] 1)
127+
128+
(aget proxied-vec 1)
129+
130+
(time
131+
(dotimes [i 1e7]
132+
(aget proxied-vec 1)))
133+
134+
(def proxied-deep (proxy [{:foo "Hello"}]))
135+
(-> proxied-deep (aget 0) (unchecked-get "foo"))
136+
137+
)

src/main/cljs/cljs/proxy/impl.cljs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
(ns cljs.proxy.impl)
2+
3+
(deftype SimpleCache [^:mutable obj ^:mutable cnt]
4+
Object
5+
(set [this k v]
6+
(when (== cnt 1024)
7+
(.clear this))
8+
(unchecked-set obj k v)
9+
(set! cnt (inc cnt))
10+
v)
11+
(get [this k]
12+
(unchecked-get obj k))
13+
(clear [this]
14+
(set! obj #js {})
15+
(set! cnt 0)))

0 commit comments

Comments
 (0)