Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala.js native Javascript constructors

Scala.js facade for a native JS types can look like this (from Three.js facade):

@js.native
@JSName("THREE.Vector3")
class Vector3 extends Vector {
  def this(x: Double = js.native, y: Double = js.native, z: Double = js.native) = this()
  var x: Double = js.native
  var y: Double = js.native
  var z: Double = js.native

/* ... */
}

The corresponding Javascript definition of a function constructing Vector3 is:

function Vector3( x, y, z ) {

    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0;

}

I have read docs about creating Scala.js facades, however constructors are only briefly mentioned there. The code from the facade works fine in real code, however I am unsure if the definition is correct and why and how it works.

  • the facade lets no argument constructor exist.
  • the constructor with arguments just calls a no argument constructor. Still the object seems to be constructed fine, with the member set to the values passed.
  • the constructor uses js.native as default value for all arguments. Should all facades define constructors this way?

Esp. the second point is confusing to me. How can this be working? In all three cases I would like to know what JS code is generated for the constructor and why.

One could also imagine a different way how to write the facade. Would that be more correct?

class Vector3(var x: Double = js.native, var y: Double = js.native, var z: Double = js.native) extends Vector {

/* ... */
}
like image 720
Suma Avatar asked Sep 20 '25 13:09

Suma


1 Answers

The definition is correct. The rules of facades for JavaScript constructors are quite simply stated: when encountering a call such as

new C(arg1, ..., argN)

and C is a JavaScript class, this translates to

new Cconstr(arg1, ..., argN)

where Cconstr is the result of evaluating js.constructorOf[C]. For a native JavaScript class, js.constructorOf[C] looks up the name of C in the global scope (or applies @JSName or @JSImport rules). Concretely, in your example, a call such as

new Vector3(3, 4, 5)

translates to

new <global>.THREE.Vector3(3, 4, 5)

Note, in particular, that the body of the constructor definitions is completely irrelevant, since the call site directly calls JavaScript code in the Three.js library. Hence, the fact that the 3-arg constructor calls the 0-arg constructor and ignores its arguments is simply disregarded by the semantic rules. The call is necessary to comply with Scala's type checking rules, but is semantically irrelevant.

Similarly, the actual value of default parameter values are semantically irrelevant. Their presence makes the parameters optional, but their value is otherwise ignored by the compiler. A call such as

new Vector3(3)

translates in JavaScript to

new <global>.THREE.Vector3(3)

in which the y and z parameters are altogether not given, leaving to JavaScript to decide what to do with them.

Finally, your alternative definition:

class Vector3(var x: Double = js.native, var y: Double = js.native, var z: Double = js.native)

is just equally valid. It doesn't have an explicit 0-arg constructor, but it can also be "accessed" by giving 0 argument to the constructor, which has 3 optional parameters anyway. This definition is certainly more concise, though, and looks a bit more Scala-esque, so I would personally define it that way. But it is not any more correct than the initial one.

like image 95
sjrd Avatar answered Sep 23 '25 07:09

sjrd