Class JSObject
java.lang.Object
org.graalvm.webimage.api.JSValue
org.graalvm.webimage.api.JSObject
A Passing
When an object of the
JSObject
is a Java-side wrapper for JavaScript objects. The purpose of this class
is that a JavaScript object is not normally an instance of any Java class, and it therefore
cannot be represented as a data-type in Java programs. When the JavaScript code (invoked by the
method annotated with the JS
annotation) returns a JavaScript object, that object gets
wrapped into a JSObject
instance. The JSObject
allows the Java code to
access the fields of the underlying JavaScript object using the get
and
set
methods.
The Java JSObject
instance that corresponds to the JavaScript object is called a Java
mirror. Vice versa, the JavaScript instance is a JavaScript mirror for that
JSObject
instance.
Anonymous JavaScript objects
Here are a few examples of creating and modifying anonymous JavaScript objects:@JS("return {x: x, y: y};") public static native JSObject createPair(double x, double y); JSObject pair = createPair(3.2, 4.8); System.out.println(pair.get("x")); System.out.println(pair.get("y"));In this example, using the
JSObject
methods, the user can access the x
and
y
fields.
pair.set("x", 5.4); System.out.println(pair.get("x"));The code above sets a new value for the
x
field, and then prints the new value.
Anonymous JavaScript functions
AJSObject
can be a Java-side wrapper for a JavaScript Function
object. The
JavaScript Function
value can be returned by the JavaScript code of the method annotated
with the JS
annotation.
The Java program can then call the underlying function by calling the
call(Object, Object...)
method. If the underlying JavaScript object is not
callable, then calling call
will result in an exception.
The call
method has the following signature:
public Object call(Object thisArgument, Object... arguments);The
invoke
method has the following signature:
public Object invoke(Object... arguments);The difference is that the method
call
takes an object for specifying this
of the
JavaScript function, while invoke
uses the underlying JSObject
as the value of
this
.
Here is an example of how to use JSObject
as a function:
public static void main(String[] args) { JSObject function = createFunction(); Object result = function.invoke(JSNumber.of(1), JSNumber.of(2)); System.out.println(result); } @JS("return (a, b) => { return a + b; };") public static native JSObject createFunction();
The JS
-annotated method createFunction
in the preceding example creates a
function in JavaScript that adds together two numbers. The call to createFunction
returns
a JSObject
object, upon which users can call the invoke
method to execute the
underlying JavaScript function. The result JavaScript<number; 3<
is then printed.
Declaring JavaScript classes in Java
The second purpose ofJSObject
is to declare JavaScript classes as classes in Java code,
in a way that makes the Java objects look-and-feel like native JavaScript objects. Users should
subclass the JSObject
class when they need to define a JavaScript class whose fields and
methods can be accessed conveniently from Java, without the get(Object)
and
set(Object, Object)
methods.
Directly exposing a Java object to JavaScript code means that the JavaScript code is able to
manipulate the data within the object (e.g. mutate fields, add new fields, or redefine existing
fields), which is not allowed by default for regular Java classes. Extending JSObject
furthermore allows the JavaScript code to instantiate objects of the JSObject
subclass.
One of the use-cases for these functionalities are JavaScript frameworks that redefine properties
of JavaScript objects with custom getters and setters, with the goal of enabling data-binding or
reactive updates.
In a subclass of JSObject
, every JavaScript property directly corresponds to the Java
field of the same name. Consequently, all these properties point to native JavaScript values
rather than Java values, so bridge methods are generated that are called for each property access
and that convert native JavaScript values to their Java counterparts. The conversion rules are
the same as in a JS
-annotated method. Furthermore, note that JavaScript code can violate
the Java type-safety by storing into some property a value that is not compatible with the
corresponding Java field. For this reason, the bridge methods also generate check-casts on every
access: if the JavaScript property that corresponds to the Java field does not contain a
compatible value, a ClassCastException
is thrown.
There are several restrictions imposed on JSObject
subclasses:
- Only public and protected fields are allowed to ensure encapsulation.
- Instance fields must not be
final
. This restriction ensures that JavaScript code cannot inadvertently change the property that corresponds to a final field.
JSObject
subclass:
public class Point extends JSObject { protected double x; protected double y; public Point(double x, double y) { this.x = x; this.y = y; } public double absolute() { return Math.sqrt(x * x + y * y); } }The preceding Java class is effectively translated to the corresponding JavaScript class:
class Point { constructor(x, y){ this.x=x; this.y=y; } absolute() { // Java code that computes sqrt(x * x + y * y); } }The
Point
class can be used from Java as if it were a normal Java class:
Point p = new Point(0.3, 0.4); System.out.println(p.x); System.out.println(p.y); System.out.println(p.absolute());All accesses to the fields
x
and y
are rewritten to accesses on the corresponding
JavaScript properties. JavaScript code may assign values to these properties that violate the
type of the corresponding Java fields, but a subsequent Java read of such a field will result in
a ClassCastException
.
@JS("p.x = s;") public static native void corrupt(Point p, String s); corrupt(p, "whoops"); System.out.println(p.x); // Throws a ClassCastException.
Passing JSObject
subclasses between JavaScript and Java
When an object of the JSObject
subclass is passed from Java to JavaScript code using the
JS
annotation, the object is converted from its Java representation to its JavaScript
representation. Changes in the JavaScript representation are reflected in the Java representation
and vice-versa.
Example: the following code resets the Point
object in JavaScript by calling the
reset
method, and then prints 0, 0
:
@JS("p.x = x; p.y = y;") public static native void reset(Point p, double x, double y); Point p0 = new Point(1.5, 2.5); reset(p0, 0.0, 0.0); System.out.println(p0.x + ", " + p0.y);A
Class
object that represents JSObject
can also be passed to JavaScript code.
The Class
object is wrapped in a proxy, which can be used inside a new
expression
to instantiate the object of the corresponding class from JavaScript.
Example: the following code creates a Point
object in JavaScript:
@JS("return new pointType(x, y);") public static Point create(ClassNote that creating an object in JavaScript and passing it to Java several times does not guarantee that the same mirror instance is returned each time -- each time a JavaScript object becomes a Java mirror, a different instance of the mirror may be returned.pointType, double x, double y); Point p1 = create(Point.class, 1.25, 0.5); System.out.println(p1.x + ", " + p1.y);
Importing existing JavaScript classes
TheJSObject
class allows access to properties of any JavaScript object using the
get(Object)
and set(Object, Object)
methods. In situations
where the programmer knows the relevant properties of a JavaScript object (for example, when
there is a class corresponding to the JavaScript object), the object's "blueprint" can be
"imported" to Java. To do this, the user declares a JSObject
subclass that serves as a
facade to the JavaScript object. This subclass must be annotated with the JS.Import
annotation.
The name of the declared class Java must match the name of the JavaScript class that is being
imported. The package name of the Java class is not taken into account -- the same JavaScript
class can be imported multiple times from within separate packages.
The exposed JavaScript fields must be declared as protected
or public
. The
constructor parameters must match those of the JavaScript class, and its body must be empty.
Here is an example of a class declared in JavaScript:
class Rectangle { constructor(width, height) { this.width = width; this.height = height; } }To import this class in Java code, we need the following declaration in Java:
@JS.Import public class Rectangle extends JSObject { protected int width; protected int height; public Rectangle(int width, int height) { } }The fields declared in the
Rectangle
class are directly mapped to the properties of the
underlying JavaScript object. If the type of the property of the underlying JavaScript object
does not match the type of the field declared in Java, then a field-read in Java will throw a
ClassCastException
.
The Rectangle
class can be instantiated from Java as follows:
Rectangle r = new Rectangle(640, 480); System.out.println(r.width + "x" + r.height);A JavaScript object whose
constructor
property matches the imported JavaScript class can
be converted to the declared Java class when the JavaScript code passes a value to Java. Here is
a code example that creates the Rectangle
object in JavaScript, and passes it to Java:
@JS("return new Rectangle(width, height);") Rectangle createRectangle(int width, int height);Another way to convert a JavaScript object to a Java facade is to call the
as(Class)
method to cast the JSObject
instance to the proper subtype.
Exporting Java classes to JavaScript
The users can annotate the exported classes with theJS.Export
annotation to denote that
the JSObject
subclass should be made available to JavaScript code. Exported classes can
be accessed using the JavaScript VM-instance API, using the `exports` property.
Example: the following code exports a Java class:
package org.example; @JS.Export public class Randomizer extends JSObject { private Random rng = new Random(719513L); public byte[] randomBytes(int length) { byte[] bytes = new byte[length]; rng.nextBytes(bytes); return bytes; } }The exported class can then be used from JavaScript code as follows:
GraalVM.run([]).then(vm => { const Randomizer = vm.exports.org.example.Randomizer; const r = new Randomizer(); const bytes = r.randomBytes(1024); });
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescription<T> T
boolean[]
byte[]
char[]
double[]
float[]
int[]
long[]
short[]
Calls the underlying JavaScript function with the given value for the binding ofthis
in the function, if this object is callable.static JSObject
create()
Creates an empty JavaScript object.boolean
equalsJavaScript
(JSObject that) Returns the value of the key passed as the argument in the JavaScript object.Invoke the underlying JavaScript function, if this object is callable.keys()
Returns the array of property keys for this object.void
Sets the value of the key passed as the argument in the JavaScript object.protected String
typeof()
-
Constructor Details
-
JSObject
protected JSObject()
-
-
Method Details
-
create
Creates an empty JavaScript object.- Returns:
- an empty JavaScript object
-
typeofString
-
typeof
-
stringValue
- Specified by:
stringValue
in classJSValue
-
get
-
set
Sets the value of the key passed as the argument in the JavaScript object.- Parameters:
key
- the object under which the value should be placed in the JavaScript objectnewValue
- the value that should be placed under the given key in the JavaScript object
-
keys
Returns the array of property keys for this object.- Returns:
- an array of all the keys that can be used with
get
on this object
-
invoke
Invoke the underlying JavaScript function, if this object is callable.- Parameters:
args
- The array of Java arguments, which is converted to JavaScript and passed to the underlying JavaScript function- Returns:
- The result of the JavaScript function, converted to the corresponding Java value
-
call
Calls the underlying JavaScript function with the given value for the binding ofthis
in the function, if this object is callable.- Parameters:
thisArg
- The value for the binding ofthis
inside the JavaScript functionargs
- The array of Java arguments, which is converted to JavaScript and passed to the underlying JavaScript function- Returns:
- The result of the JavaScript function, converted to the corresponding Java value
-
asBooleanArray
public boolean[] asBooleanArray()- Overrides:
asBooleanArray
in classJSValue
-
asByteArray
public byte[] asByteArray()- Overrides:
asByteArray
in classJSValue
-
asShortArray
public short[] asShortArray()- Overrides:
asShortArray
in classJSValue
-
asCharArray
public char[] asCharArray()- Overrides:
asCharArray
in classJSValue
-
asIntArray
public int[] asIntArray()- Overrides:
asIntArray
in classJSValue
-
asFloatArray
public float[] asFloatArray()- Overrides:
asFloatArray
in classJSValue
-
asLongArray
public long[] asLongArray()- Overrides:
asLongArray
in classJSValue
-
asDoubleArray
public double[] asDoubleArray()- Overrides:
asDoubleArray
in classJSValue
-
as
-
equalsJavaScript
-