Skip to content

Working with Javascript classes

Kristian Mandrup edited this page Feb 19, 2016 · 2 revisions

Most ClojureScript developers will at some point have to work with existing JavaScript libraries or frameworks which require an OOP approach. So you sometimes have to work with Javascript "classes" (ie. prototype hierarchies) from within your ClojureScript.

To create a class Bag with setter functions add and print, you might first come up with something like the following (which looks ugly as hell). There are thankfully much cleaner ways to achieve the same effect as we will demonstrate below.

(defn Bag []
  (this-as this
           (set! (.-store this) (array))
           this))
  
(set! (.. Bag -prototype -add)
      (fn [val]
        (this-as this
                 (.push (.-store this) val))))
  
(set! (.. Bag -prototype -print)
      (fn []
        (this-as this
                 (.log js/console (.-store this)))))
  
(def mybag (Bag.))
(.add mybag 5)
(.add mybag 7)
(.print mybag)

You can use protocols to provide namespaced methods and a more idiomatic syntax:

(defprotocol MyBag
  (add [this val])
  (print [this]))

(extend-type Bag
  MyBag
  (add [this val]
  (.push (.-store this) val))
  (print [this]
  (.log js/console (.-store this))))

(def mybag (Bag.))
(add mybag 2)
(add mybag 3)
(print mybag)

You can also use deftype and the special Object protocol.

(deftype Bag [store]
  Object
  (add [_ x] (.push store x))
  (print [_] (.log js/console store)))
 
(defn bag [arr] (Bag. arr))

The Object protocol can also be used with reify for a pure functional solution.

(defn bag [store]
  (reify
    Object
    (add [this x] (.push store x))
    (print [this x] (.log js/console store))))

If you want some state to be fully encapsulated and kept private, you can refactor the constructor function as follows.

(defn bag []
  (let [store (create-store)]
    (reify
      Object
      (add [this x] (.push store x))
      (print [this x] (.log js/console store)))))

This makes store a local variable (closure), set by calling the function create-store (which you must define). Happy Clojure!

Clone this wiki locally