Understanding how different types are mapped between Scala.js and JavaScript is crucial for correct interoperability.Some types map quite directly (like String
) where others require some conversions.
Type Correspondence
Some Scala types are directly mapped to corresponding underlying JavaScript types. These correspondences can be usedwhen calling Scala.js code from JavaScript and when defining typed interfaces for JavaScript code.
Scala type | JavaScript type | Restrictions |
---|---|---|
java.lang.String | string | |
scala.Boolean | boolean | |
scala.Char | opaque | |
scala.Byte | number | integer, range (-128, 127) |
scala.Short | number | integer, range (-32768, 32767) |
scala.Int | number | integer, range (-2147483648, 2147483647) |
scala.Long | opaque | |
scala.Float | number | |
scala.Double | number | |
scala.Unit | undefined | |
scala.Null | null | |
subtypes of js.Any | themselves | see the facade types guide |
other Scala classesincluding value classes | opaque, except for exported methodsNote: toString() is always exported | see exporting Scala.js APIs to JavaScript |
On the other hand, some JavaScript (collection) types have similar types in Scala.Instead of mapping them directly, Scala.js provides conversions between them.We show with a couple of snippets how you can convert from JavaScript to Scala types and back.Please refer to the Scaladocs for details.
js.FunctionN <–> scala.FunctionN
Functions from JavaScript and Scala are not exactly the same thing, thereforethey have different types. However, implicit conversions are available bydefault to go from one to the other, which means the following snippets compileout of the box:
import scala.scalajs.js
val scalaFun: Int => Int = (x: Int) => x * x
val jsFun: js.Function1[Int, Int] = scalaFun
val scalaFunAgain: Int => Int = jsFun
Most of the time, you don’t even need to worry about those, except if youwrite facade types for JavaScript APIs, in which case youhave to use the JS function types.
js.Array[T] <–> mutable.Seq[T]
import scala.scalajs.js
val jsArr = js.Array(1, 2, 3)
// Scala style operations on js.Array (returns a js.Array)
val x: js.Array[Int] = jsArr.takeWhile(_ < 3)
// Use a js.Array as a Scala mutable.Seq
val y: mutable.Seq[Int] = jsArr
// toArray (from js.ArrayOps) -- Copy into scala.Array
val z: scala.Array[Int] = jsArr.toArray
import js.JSConverters._
val scSeq = Seq(1, 2, 3)
// Seq to js.Array -- Copy to js.Array
val jsArray: js.Array[Int] = scSeq.toJSArray
js.Dictionary[T] <–> mutable.Map[String, T]
import scala.scalajs.js
val jsDict = js.Dictionary("a" -> 1, "b" -> 2)
// Scala style operations on js.Dictionary (returns mutable.Map)
val x: mutable.Map[String, Int] = jsDict.mapValues(_ * 2)
// Use a js.Dictionary as Scala mutable.Map
val y: mutable.Map[String, Int] = jsDict
import js.JSConverters._
val scMap = Map("a" -> 1, "b" -> 2)
// Map to js.Dictionary -- Copy to js.Dictionary
val jsDictionary: js.Dictionary[Int] = scMap.toJSDictionary
js.UndefOr[T] <–> Option[T]
import scala.scalajs.js
val jsUndefOr: js.UndefOr[Int] = 1
// Convert to scala.Option
val x: Option[Int] = jsUndefOr.toOption
import js.JSConverters._
val opt = Some(1)
// Convert to js.Undefined
val y: js.UndefOr[Int] = opt.orUndefined
Pre-defined JavaScript types
Primitive JavaScript types (number
, boolean
, string
, null
andundefined
) are represented by their natural equivalent in Scala, as shownabove.
For other pre-defined JavaScript types, such as arrays and functions, the package scala.scalajs.js
(ScalaDoc)provides dedicated definitions.
The class hierarchy for these standard types is as follows:
js.Any
+- js.Object
| +- js.Date
| +- js.RegExp
| +- js.Array[A]
| +- js.Function
| | +- js.Function0[+R]
| | +- js.Function1[-T1, +R]
| | +- ...
| | +- js.Function22[-T1, ..., -T22, +R]
| | +- js.ThisFunction
| | +- js.ThisFunction0[-T0, +R]
| | +- js.ThisFunction1[-T0, -T1, +R]
| | +- ...
| | +- js.ThisFunction21[-T0, ..., -T21, +R]
| +- js.Iterable[+A]
| +- js.Iterator[+A]
| +- js.Promise[+A]
| +- js.Thenable[+A]
+- js.Dictionary[A]
+- js.Symbol
Note that most of these types are similar to standard Scala types. For example,js.Array[A]
is similar to scala.Array[A]
, and js.FunctionN
is similar toscala.FunctionN
. However, they are not completely equivalent, and must not be confused.
With the exception of js.Array[A]
and js.Dictionary[A]
, these types haveall the fields and methods available in the JavaScript API.The collection types feature the standard Scala collection API instead, so thatthey can be used idiomatically in Scala code.
Function types
js.Function and its subtypes
js.FunctionN[T1, …, TN, R]
is, as expected, the type of a JavaScriptfunction taking N parameters of types T1
to TN
, and returning a value oftype R
.
There are implicit conversions from scala.FunctionN
to js.FunctionN
andback, with the obvious meaning.These conversions are the only way to create a js.FunctionN
in Scala.js.For example:
val f: js.Function1[Double, Double] = { (x: Double) => x*x }
defines a JavaScript function
object which squares its argument.This corresponds to the following JavaScript code:
var f = function(x) {
return x*x;
};
You can call a js.FunctionN
in Scala.js with the usual syntax:
val y = f(5)
js.ThisFunction and its subtypes
The series of js.ThisFunctionN
solve the problem of modeling the this
value of JavaScript in Scala. Consider the following call to the each
methodof a jQuery object:
var lis = jQuery("ol > li");
lis.each(function() {
jQuery(this).text(jQuery(this).text() + " - transformed")
});
Inside the closure, the value of this
is the DOM element currently beingenumerated. This usage of this
, which is nonsense from a Scala point of view,is standard in JavaScript. this
can actually be thought of as an additionalparameter to the closure.
In Scala.js, the this
keyword always follows the same rules as in Scala,i.e., it binds to the enclosing class, trait or object. It will never bind tothe equivalent of the JavaScript this
in an anonymous function.
To access the JavaScript this
in Scala.js, it can be made explicit usingjs.ThisFunctionN
. A js.ThisFunctionN[T0, T1, …, TN, R]
is the type of aJavaScript function taking a this
parameter of type T0
, as well as Nnormal parameters of types T1
to TN
, and returning a value of type R
.From Scala.js, the this
parameter appears as any other parameter: it has anon-keyword name, a type, and is listed first in the parameter list. Hence,a scala.FunctionN
is convertible to/from a js.ThisFunction{N-1}
.
The previous example would be written as follows in Scala.js:
val lis = jQuery("ol > li")
lis.each({ (li: dom.HTMLElement) =>
jQuery(li).text(jQuery(li).text() + " - transformed")
}: js.ThisFunction)
Skipping over the irrelevant details, note that the parameter li
completelycorresponds to the JavaScript this
. Note also that we have ascribed thelambda with : js.ThisFunction
explicitly to make sure that the right implicitconversion is being used (by default it would convert it to a js.Function1
).If you call a statically typed API which expects a js.ThisFunction0
, this isnot needed.
The mapping between JS this
and first parameter of a js.ThisFunction
alsoworks in the other direction, i.e., if calling the apply
method of ajs.ThisFunction
, the first actual argument is transferred to the calledfunction as its this
. For example, the following snippet:
val f: js.ThisFunction1[js.Object, Int, Int] = ???
val o = new js.Object
val x = f(o, 4)
will map to
var f = ...;
var o = new Object();
var x = f.call(o, 4);
Dynamically typed interface: js.Dynamic
Because JavaScript is dynamically typed, it is not often practical, sometimesimpossible, to give sensible type definitions for JavaScript APIs.
Scala.js lets you call JavaScript in a dynamically typed fashion if youwant to. The basic entry point is js.Dynamic.global
, which is a dynamicallytyped view of the JavaScript global scope. You can select any global variableof JavaScript as a a member of js.Dynamic.global
, e.g.,js.Dynamic.global.Math
, which will be typed as ajs.Dynamic
.
You can read and write any field of a js.Dynamic
, as well as call any methodwith any number of arguments, and you always receive back a js.Dynamic
.
For example, this snippet taken from the Hello World example uses thedynamically typed interface to manipulate the DOM model.
val document = js.Dynamic.global.document
val playground = document.getElementById("playground")
val newP = document.createElement("p")
newP.innerHTML = "Hello world! <i>-- DOM</i>"
playground.appendChild(newP)
In this example, document
, playground
and newP
are all inferred to be oftype js.Dynamic
.
Literal object construction
Scala.js provides two syntaxes for creating JavaScript objects in a literalway. The following JavaScript object
{foo: 42, bar: "foobar"}
can be written in Scala.js either as
js.Dynamic.literal(foo = 42, bar = "foobar")
or as
js.Dynamic.literal("foo" -> 42, "bar" -> "foobar")
Literal object construction using an Scala object interface
Sometimes for a nicer interface, literal objects can be implemented usinga trait interface.The above JavaScript code can be implemented using following code:
trait MyObject extends js.Object {
val foo: Int = js.native
val bar: String = js.native
}
A Scala object should be added for typesafe creation, it would help the readabilityof the code by removing lots of js.Dynamic.literal
all over the code.
object MyObject {
def apply(foo: Int, bar: String): MyObject =
js.Dynamic.literal(foo = foo, bar = bar).asInstanceOf[MyObject]
}
Alternatively, you can use anonymous classes extending js.Object
or aScala.js-defined JS trait.