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(Class pointType, double x, double y);
Point p1 = create(Point.class, 1.25, 0.5);
System.out.println(p1.x + ", " + p1.y);
Note 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.
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> Tboolean[]byte[]char[]double[]float[]int[]long[]short[]Calls the underlying JavaScript function with the given value for the binding ofthisin the function, if this object is callable.static JSObjectcreate()Creates an empty JavaScript object.booleanequalsJavaScript(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.voidSets the value of the key passed as the argument in the JavaScript object.protected Stringtypeof()
-
Constructor Details
-
JSObject
protected JSObject()
-
-
Method Details
-
create
Creates an empty JavaScript object.- Returns:
- an empty JavaScript object
-
typeofString
-
typeof
-
stringValue
- Specified by:
stringValuein 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
geton 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 ofthisin the function, if this object is callable.- Parameters:
thisArg- The value for the binding ofthisinside 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:
asBooleanArrayin classJSValue
-
asByteArray
public byte[] asByteArray()- Overrides:
asByteArrayin classJSValue
-
asShortArray
public short[] asShortArray()- Overrides:
asShortArrayin classJSValue
-
asCharArray
public char[] asCharArray()- Overrides:
asCharArrayin classJSValue
-
asIntArray
public int[] asIntArray()- Overrides:
asIntArrayin classJSValue
-
asFloatArray
public float[] asFloatArray()- Overrides:
asFloatArrayin classJSValue
-
asLongArray
public long[] asLongArray()- Overrides:
asLongArrayin classJSValue
-
asDoubleArray
public double[] asDoubleArray()- Overrides:
asDoubleArrayin classJSValue
-
as
-
equalsJavaScript
-