What's the Fastest Way to Create JavaScript Types in Scala.js? :: Feb 10, 2020
In Scala.js , we cannot just write
class C ( val a : String , val b : Boolean , val c : Int )
import scala.scalajs.js.JSON
JSON . stringify ( C ( "hello world" , true , 69 ))
// (this post uses Scala 3 syntax)
It’s because JSON.stringify takes a native JavaScript parameter of type js.Any
. So, what’s the fastest way to create a native JavaScript type in Scala.js?
Option 1: Subclass of js.Object .
import scala.scalajs.js
class C ( val a : String , val b : Boolean , val c : Int ) extends js . Object
JSON . stringify ( C ( "hello world" , true , 69 ))
Option 2: Dynamic.literal with tupled arguments.
val literal : js.Dynamic = js . Dynamic . literal ( "a" -> "hello world" , "b" -> true , "c" -> 69 )
JSON . stringify ( literal )
Option 3: Dynamic.literal
with varargs.
val literal : js.Dynamic = js . Dynamic . literal ( a = "hello world" , b = true , c = 69 )
JSON . stringify ( literal )
Option 4: Dynamic.literal
with Dynamic::updateDynamic .
val literal = js . Dynamic . literal ()
literal . updateDynamic ( "a" )( "hello world" )
literal . updateDynamic ( "b" )( true )
literal . updateDynamic ( "c" )( 69 )
JSON . stringify ( literal )
Option 5: js.Object
with Dynamic::updateDynamic
val dynamicObj = js . Object (). asInstanceOf [ js.Dynamic ]
dynamicObj . updateDynamic ( "a" )( "hello world" )
dynamicObj . updateDynamic ( "b" )( true )
dynamicObj . updateDynamic ( "c" )( 69 )
JSON . stringify ( dynamicObj )
Guess now before scrolling!
This result makes sense to me after looking at the codegen. Subclasses have initialization, updateDynamic
is intrinsically translated into bracket access like literal['a'] = 'hello world'
, and Dynamic.literal()
does not have a non-varargs overload unlike js.Object()
.
Full Benchmark Source:
import scala.scalajs.js
import scala.scalajs.js.JSON
import scala.util.Random
class C ( val a : String , val b : Boolean , val c : Int ) extends js . Object
@main def bench () : Unit =
for _ <- 1 to 20 do loop ()
def loop () : Unit =
val limit = 1000000
val res = Array . ofDim [ String ]( limit )
var i = 0
var start = js . Date . now ()
while i < limit do
res ( i ) = JSON . stringify ( C ( "hello world" , true , 69 ))
i += 1
var end = js . Date . now ()
def randomJson : String = res ( Random . nextInt ( res . size ))
println ( s "ms to make $randomJson classes: ${end - start}" )
i = 0
start = js . Date . now ()
while i < limit do
res ( i ) = JSON . stringify ( js . Dynamic . literal ( "a" -> "hello world" , "b" -> true , "c" -> 69 ))
i += 1
end = js . Date . now ()
println ( s "ms to make $randomJson literals via tuples: ${end - start}" )
i = 0
start = js . Date . now ()
while i < limit do
res ( i ) = JSON . stringify ( js . Dynamic . literal ( a = "hello world" , b = true , c = 69 ))
i += 1
end = js . Date . now ()
println ( s "ms to make $randomJson literals: ${end - start}" )
i = 0
start = js . Date . now ()
while i < limit do
val literal = js . Dynamic . literal ()
literal . updateDynamic ( "a" )( "hello world" )
literal . updateDynamic ( "b" )( true )
literal . updateDynamic ( "c" )( 69 )
res ( i ) = JSON . stringify ( literal )
i += 1
end = js . Date . now ()
println ( s "ms to make $randomJson literals via updateDynamic: ${end - start}" )
i = 0
start = js . Date . now ()
while i < limit do
val dynamicObj = js . Object (). asInstanceOf [ js.Dynamic ]
dynamicObj . updateDynamic ( "a" )( "hello world" )
dynamicObj . updateDynamic ( "b" )( true )
dynamicObj . updateDynamic ( "c" )( 69 )
res ( i ) = JSON . stringify ( dynamicObj )
i += 1
end = js . Date . now ()
println ( s "ms to make $randomJson objects with js.Object() + updateDynamic: ${end - start}" )
println ()
Why does any of this matter? Next blog we will see!