Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add reference/javascript-interop-ref.adoc #95

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
456 changes: 456 additions & 0 deletions content/reference/javascript-interop-ref.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,456 @@
= Javascript Interop Reference
Gregg Reynolds
2017-06-25
:type: reference
:toc: macro
:icons: font

ifdef::env-github,env-browser[:outfilesuffix: .adoc]

WARNING: Work-in-Progress

toc::[]


[[sources]]
== Dev Notes

This section is temporary, and will be deleted when this page is finished.

Goals:

* concise, complete reference, parallel to https://clojure.org/reference/java_interop[Java Interop] for Clojure
* consolidate here the interop-related info that is currently
scattered across this site (e.g. the https://github.com/clojure/clojurescript/wiki[wiki], link:../about/differences.adoc[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 link:../guides/javascript-interop-guide.adoc[Javascript Interop Guide]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(when somebody creates it :))

Copy link
Contributor

@holyjak holyjak Jan 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


=== content

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 https://dev.clojure.org/display/design/this[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 http://www.spacjer.com/blog/2014/09/12/clojurescript-javascript-interop/[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.

=== sources

* https://dev.clojure.org/display/design/this[Problem 1: ClojureScript functions as object methods]
* http://www.spacjer.com/blog/2014/09/12/clojurescript-javascript-interop/[ClojureScript: JavaScript Interop] (Rafal Spacjer blog)
* http://squirrel.pl/blog/2013/03/28/two-ways-to-access-properties-in-clojurescript/[Two Ways to Access Properties in ClojureScript]
* https://github.com/cljs/api/issues/128[aget not to be used on objects]
* http://clojurescriptmadeeasy.com/blog/when-do-i-use-require-vs-import.html[When do I use :require vs :import]
* http://cljs.info/cheatsheet/[cljs cheatsheet]
* https://clojure.org/reference/java_interop[Clojure Java Interop]
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects[MDN Standard built-in objects]



[[overview]]
== Overview

TODO: something about dealing with different JS versions.

See
https://clojurescript.org/reference/compiler-options#language-in-and-language-out[:language-in
and :language-out] on the Compiler Options reference page: "Configure
the input and output languages for the closure library."



[[cljs-to-js]]
== Clojurescript to Javascript

Clojurescript interop is similar to Clojure/Java interop; much of
the information on Clojure's
https://clojure.org/reference/java_interop[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` and `Math`. All
global variables are registered in the former; the latter is a
convenience namespace for the standard JS `Math` object.
* Javascript comes with a collection of
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects[Standard
built-in objects] that correspond to Java library classes, such as
`Math` and `String`. All except `Math` must be namespaced with `js`.
* Javascript does not have static properties


=== #js

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}


[source,clojurescript]
----
#js {"a" 9}
-> #js {:a 9}
#js [1 2]
-> #js [1 2]
----

=== js*

TODO: describe

=== js-*

TODO: describe the js- prefixed operators: js-arguments, js-comment, etc.

=== JS value construction

==== JS Arrays

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 size `sz`
* `(aclone arr)` ;; shallow-copy the javascript array `arr`

[source,clojurescript]
----
#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]
----

==== JS Objects

There are two basic ways to create a javascript object:

* `#js { &keyvals }`
* `(js-obj &keyvals)`: create JavaSript object from an even number arguments
representing interleaved keys and values.

[source,clojurescript]
----
#js {:a 1 :b 2}
-> #js {:a 1, :b 2}
(js-obj :a 1 :b 2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that for js-obj we need to use string keys, otherwise we end up with the js object { ':a': 1, ':b': 2 }, which likely is not desriable. Worth pointing out that #js converts :kwd -> "kwd" (and ::kwd -> ":kwd".

We should also mentioned that #js is not recursive, i.e. I need #js {:child #js {:isKid true} :isParent true}

-> #js {::a 1, ::b 2}
----

=== JS Object Property access

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".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also remind the user that objects in the global context need to be accessed via js/, contrary to the direct access in JS itself?

[%hardbreaks]
**(.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?

[source,clojure]
----
(.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:

[source,clojurescript]
----
(.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)
----


[[nested]]
=== Compound (nested) Structures

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.

.clj->js conversions
[cols=4]
|===
2+| clojurescript 2+| javascript

| set | #{} | Array | []
| vector | [] | Array | []
| list | () | Array | []
| keyword | :foo | String | "foo"
| Symbol | bar | String | "bar"
| Map | {} | Object | {}
|===

TODO: examples

=== goog.object

TODO: make this readable

For interacting with Javascript objects, use `goog.object` rather than `aget`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps also mention https://github.com/binaryage/cljs-oops as an alternative? It is already mentioned at cljs.info/cheatsheet/


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: https://github.com/cljs/api/issues/128[aget is not to be used on objects]

== The Dot special form

TODO: this is from the Clojure interop page - adapt it to cljs

[%hardbreaks]
*(_._ 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.

''''

[%hardbreaks]
*(_.._ 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 https://clojure.github.com/clojure/clojure.core-api.html#clojure.core/%2d%3e[pass:[->]] macro which can be used similarly:

`(pass:[->] (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 https://cljs.github.io/api/cljs.core/#doto[cljs api ref] but it uses java; TODO: fix the api doc

[source,clojure]
----
(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
-> {a=1, b=2}
----

[[new]]
''''

[%hardbreaks]
*(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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • this:

Note that if Typename is defined within a module, you simply prepend the module name with a dot, just as you would do in JavaScript: (new myModule.MyType ...)

=== Alternative Macro Syntax

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]]
''''

[%hardbreaks]
*(_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

''''

=== types and classes

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:

* https://github.com/clojure/clojurescript/wiki/Working-with-Javascript-classes[Working with Javascript classes]
* https://stackoverflow.com/questions/9018326/how-do-i-create-an-js-object-with-methods-and-constructor-in-clojurescript[How do I create an JS Object with methods and constructor in ClojureScript] ]



[[js-to-cljs]]
== Javascript to Clojurescript

=== Scopes and Namespaces

TODO: brief overview of namespaces in Clojure and Clojurescript

TODO: brief explication of Google Closure namespacing mechanism


== Using Javascript Libraries

=== Google Closure Library

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 https://clojurescript.org/reference/dependencies[Dependencies]