Warning
|
Work-in-Progress |
This section is temporary, and will be deleted when this page is finished.
Goals:
-
concise, complete reference, parallel to Java Interop for Clojure
-
consolidate here the interop-related info that is currently scattered across this site (e.g. the wiki, Differences from Clojure)
-
adapt Java-oriented language in the Clojure docs to JS-oriented language:
-
type not class
-
object not instance
-
field property, not member
-
method property, not method member
-
etc.
-
Non-goals:
-
user guide - that goes in Javascript Interop Guide
checklist:
-
namespaces
-
cljs leverages Closure’s mechanism
-
namespace does not necessarily correspond to a source path
-
js namespace
-
-
classes, types, & prototypes - e.g. what exactly does "type" mean in cljs/js?
-
"this" - this-as, (js* "this") (see Problem 1: ClojureScript functions as object methods)
-
goog.object as "built-in external" in ECMAScript terms
-
#js
-
js- prefixed ops
-
array, make-array, aclone
-
object property access
-
goog.object/geet
-
dot notation
-
aget/aset (prefer goog.object/get)
-
js-delete
-
-
js* ?? referenced in blog
-
instance?
equivalents:array?
,fn?
etc. -
external libs
-
"legacy" js, embedded inline and out-of-line (i.e. non-modularized)
-
modules - goog, CommonJS, AMD, ES6, etc.
-
-
dealing with strings?
-
use of Object in deftype: it’s treated as a Protocol (for arbitrary js methods)
-
debugging
-
:repl-verbose
-
Add: table showing mapping from JS to CLJS? e.g. intanceof → instance, in → js-in, etc.
TODO: something about dealing with different JS versions.
See :language-in and :language-out on the Compiler Options reference page: "Configure the input and output languages for the closure library."
Clojurescript interop is similar to Clojure/Java interop; much of the information on Clojure’s Java Interop page applies to Clojurescript, with a few caveats:
-
Javascript has object types, not classes
-
Javascript "objects" are not class "instances"
-
Clojurescript defines two special namespaces,
js
andMath
. All global variables are registered in the former; the latter is a convenience namespace for the standard JSMath
object. -
Javascript comes with a collection of Standard built-in objects that correspond to Java library classes, such as
Math
andString
. All exceptMath
must be namespaced withjs
. -
Javascript does not have static properties
The #js
"tagged literal" (i.e. reader tag) is the workhorse of
interop; it creates arrays and objects.
Important
|
The #js reader is non-recursive; it will not transform nested structures.
|
TODO: a note about print syntax, e.g. cljs.user> #js {:a 1 :b 2} ⇒ #js {:a 1, :b 2}
#js {"a" 9}
-> #js {:a 9}
#js [1 2]
-> #js [1 2]
There are four ways to construct a JS array:
-
#js [ … ]
;; -
(array & args)
;;(array 1 2 3)
is equiv to#js [1 2 3]
-
(make-array sz)
;; construct an empty javascript array of sizesz
-
(aclone arr)
;; shallow-copy the javascript arrayarr
#js [1 2]
-> #js [1 2]
(array 1 2 3)
-> #js [1 2 3]
(make-array 3) ;; create an empty array of length 3
#js [nil nil nil]
Where Java has classes, instances, members, and methods, Javascript has (proto)types, objects, field properties, and method properties. In the following, we will drop "property" and refer to "fields" and "methods".
(.methodProperty object args*)
(.methodProperty Typename args*) ;; FIXME: does this make sense?
(.-fieldProperty object)
(Classname/staticMethod args*) ;; FIXME: ???
Classname/staticField ;; FIXME: does not apply in js?
(.toUpperCase "fred") ;; 'toUpperCase' is a method of the JS String global object
-> "FRED"
(.charAt "fred" 2)
-> "e"
(.-length "fred") ;; 'length' is a field of string "fred"
-> 4
Math/PI ;; Special namespace for object `Math`
-> 3.141592653589793
(js/Date.) ;; Standard objects like `Date` are in the `js` namespace
#inst "2017-06-25T17:07:43.567-00:00"
(.getDate (js/Date.))
25
(.isInteger js/Number 3) ;; `Number` is another standard object
-> true
The preferred idiomatic forms for accessing field or method members are given above. The object member form works for both fields and methods. The objectField form is preferred for fields and required if both a field and a 0-argument method of the same name exist. They all expand into calls to the dot operator (described below) at macroexpansion time. The expansions are as follows:
(.methodProperty object args*) ==> (. object methodProperty args*)
(.methodProperty Typename args*) ==>
(. (identity Typename) methodProperty args*)
(.-fieldProperty object) ==> (. objec -fieldProperty)
(Typename/staticMethod args*) ==> (. Typename staticMethod args*)
Typename/staticField ==> (. Typename staticField)
TODO: brief note on preferring #js and/or js-obj
The clj→js
function recursively transforms Clojurescript values to Javascript:
Warning
|
clj→js is relatively inefficient; prefer other methods.
|
clojurescript | javascript | ||
---|---|---|---|
set |
#{} |
Array |
[] |
vector |
[] |
Array |
[] |
list |
() |
Array |
[] |
keyword |
:foo |
String |
"foo" |
Symbol |
bar |
String |
"bar" |
Map |
{} |
Object |
{} |
TODO: examples
TODO: make this readable
For interacting with Javascript objects, use goog.object
rather than aget
Here are the types and the corresponding accessors you should be using:
ILookup - get or get-in js/Array - aget js/Object - goog.object/get or goog.object/getValueByKeys
Use the right function for the right type.
(source: aget is not to be used on objects
TODO: this is from the Clojure interop page - adapt it to cljs
(. object-expr member-symbol)
(. Typename-symbol member-symbol) ;; FIXME clj only?
(. object-expr -field-symbol)
(. object-expr (method-symbol args)) or (. object-expr method-symbol args)
(. Typename-symbol (method-symbol args))* or (. Typename-symbol method-symbol args*) ;; FIXME clj only?
;; FIXME: get the asterisks right
Special form.
The '.' special form is the basis for access to Javascript Object properties. It can be considered a property-access operator, and/or read as 'in the scope of'.
Warning
|
DELETE (clj only?): If the first operand is a symbol that resolves to a class name, the access is considered to be to a static member of the named class. Note that nested classes are named EnclosingClass$NestedClass, per the JVM spec. Otherwise it is presumed to be an object member and the first argument is evaluated to produce the target object. |
Warning
|
DELETE (clj only?) For the special case of invoking an object member on a Class
object, the first argument must be an expression that evaluates to
the class object - note that the preferred form at the top expands
Classname to (identity Classname) .
|
If the second operand is a symbol it will resolve as either a field property reference or a method property reference. It it starts with `-`, it will resolve only as field property access, never as a 0-arity method. Otherwise it will resolve as a method property.
Note
|
(Here’s the original text from the clj docs) If the second operand is a symbol and no args are supplied it is taken to be a field property access - the name of the property is the name of the symbol, and the value of the expression is the value of the property, unless there is a no argument public method of the same name, in which case it resolves to a call to the method. |
If the second operand is a list, or args are supplied, it is taken to be a method call. The first element of the list must be a simple symbol, and the name of the method is the name of the symbol. The args, if any, are evaluated from left to right, and passed to the matching method, which is called, and its value returned. If the method has a void return type, the value of the expression will be nil. Note that placing the method name in a list with any args is optional in the canonic form, but can be useful to gather args in macros built upon the form.
Note that boolean return values will be turned into Booleans, chars will become Characters, and numeric primitives will become Numbers unless they are immediately consumed by a method taking a primitive.
The member access forms given at the top of this section are preferred for use in all cases other than in macros.
(.. object-expr member+)
(.. Classname-symbol member+)
member ⇒ fieldName-symbol or (objectMethodName-symbol args*)
Macro. Expands into a member access (.) of the first member on the first argument, followed by the next member on the result, etc. For instance:
(.. System (getProperties) (get "os.name"))
expands to:
(. (. System (getProperties)) (get "os.name"))
but is easier to write, read, and understand. See also the -> macro which can be used similarly:
(-> (System/getProperties) (.get "os.name"))
(doto object-expr (objectMethodName-symbol args))
Macro. Evaluates object-expr then calls all of the methods/functions with the supplied arguments in succession on the resulting object, returning it.
Note
|
this example is from the cljs api ref but it uses java; TODO: fix the api doc |
(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
-> {a=1, b=2}
(Typename. args)*
(new Typename args)*
Special form.
The args, if any, are evaluated from left to right, and passed to the constructor of the class named by Classname. The constructed object is returned.
FIXME: isn’t this redundant?
As shown, in addition to the canonic special form new, Clojure supports special macroexpansion of symbols containing '.':
(new Typename args*)
can be written
(Typename. args*) ;note trailing dot
the latter expanding into the former at macro expansion time.
(instance? c x)
Clojurescript analog to Javascript instanceof
. Evaluates x and
tests if it is an instance of the type c. Returns true or false
FIXME: what exactly does "instance of type" mean in cljs/js? express this in js langauge, e.g. (from MDN):
"The instanceof
operator tests whether an object in its prototype
chain has the prototype property of a constructor."
(set! (. object-expr fieldProperty-symbol) expr)
(set! (. Classname-symbol staticFieldName-symbol) expr) ;; FIXME: not supported in cljs?
Assignment special form.
When the first operand is a field member access form, the assignment is to the corresponding field. If it is an object field, the object expr will be evaluated, then the expr.
In all cases the value of expr is returned.
Note - you cannot assign to function params or local bindings. Only Java fields, Vars, Refs and Agents are mutable in Clojure.
FIXME: add aset, goog.object
FIXME: clarify the relation between deftype/defrecord and JS types/objects (and contrast Java classes/objects?). What is a JS "type", exactly?
FIXME: can a deftype value be passed to JS code that expects an object?
REFS:
GCL is a massive collection of JavaScript code organized into namespaces much like ClojureScript code itself. It is bundled with Clojurescript; thus, you can require a namespace from GCL in the same fashion as a ClojureScript namespace.
TODO: a note on :require v. :import
TODO: a few simple examples
TODO: refer to Interop Guide for further info
TODO: refer to Dependencies