Unlike Scala, JavaScript has a global scope, where global variables are defined.For example, one can define a variable at the top-level of a script:
which then makes it available in the global scope, so that another script can read or write it:
console.log(foo);
foo = 24;
The explains how we can define facades for global variables.Here is a recap of the different ways:
@js.native
@JSGlobalScope
object Globals extends js.Object {
var foo: Int = js.native
}
@js.native
@JSGlobal
class Bar extends js.Object
@js.native
@JSGlobal
object Bar extends js.Object
@JSGlobal
specifies that the annotated entity (class or object) represents a global variable, in the JavaScript global scope.@JSGlobalScope
specifies that the annotated object represents the global scope itself, which means its members are global variables.
With the above definitions, the snippet
val x = Globals.foo
Global.foo = 24
val y = new Bar
val z = Bar
would “translate” to
There are two “consequences” to that.
First, in any of the 4 above statements, if the referenced variable is not declared as a global variable, a ReferenceError
will be thrown at run-time.This is also what would happen in JavaScript when accessing a non-existent global variable.
After the above introduction, here is a reference of the compile-time restrictions of global-scope objects.
Assuming that Globals
is an @JSGlobalScope object
, then any use of Globals
must satisfy all of the following requirements:
- It is used as the left-hand-side of a dot-selection, i.e., in
Globals.foobar
orGlobals.foobar(…)
- Either of the 3 alternatives:
- If
foobar
refers to a method annotated with@JSBracketAccess
or@JSBracketCall
, then the first actual argument must be a constant string which is a valid JavaScript identifier (e.g.,Global.foobar("ident")
is valid butGlobal.foobar(someVal)
isn’t) - Otherwise, if
foobar
has an@JSName(jsName)
thenjsName
must be a constant string which is a valid JavaScript identifier - Otherwise,
foobar
must be a valid JavaScript identifier different thanapply
- If
For the purposes of this test, the special identifier arguments
is not considered as a valid JavaScript identifier.
Here are some concrete examples.Given the following definitions:
import scala.scalajs.js
import scala.scalajs.js.annotation._
object Symbols {
val sym: js.Symbol = js.Symbol()
}
@JSGlobalScope
object Globals extends js.Any {
def validDef(): Int = js.native
var `not-a-valid-identifier-var`: Int = js.native
def `not-a-valid-identifier-def`(): Int = js.native
def +(that: Int): Int = js.native
def apply(x: Int): Int = js.native
@JSBracketAccess
def bracketSelect(name: String): Int = js.native
@JSBracketAccess
def bracketUpdate(name: String, v: Int): Unit = js.native
@JSBracketCall
def bracketCall(name: String)(arg: Int): Int = js.native
@JSName(Symbols.sym)
var symbolVar: Int = js.native
@JSName(Symbols.sym)
def symbolDef(): Int = js.native
var arguments: js.Array[Any] = js.native
@JSName("arguments") def arguments2(x: Int): Int = js.native
}
Only the following uses of Globals
would be valid:
Globals.validVar
Globals.validDef()
Globals.bracketSelect("someConstantIdent")
Globals.bracketUpdate("someConstantIdent", anyExpression)
Globals.bracketCall("someConstantIdent")(anyExpression)
All of the following uses are compile-time errors:
// Not used as the left-hand-side of a dot-selection
val x = Globals
someMethod(Globals)
// Accessing something that is not a valid JS identifier
Globals.`not-a-valid-identifier-var`
Globals.`not-a-valid-identifier-def`()
Globals + 0
Globals.arguments
// Calling an `apply` method without `@JSName`
Globals(0)
// Accessing a bracket-access/call member with a non-constant string
val str = computeSomeString()
Globals.bracketSelect(str)
Globals.bracketCall(str)(0)
// Accessing a bracket-access/call member with a non-valid JS ident
Globals.bracketSelect("not an ident")
Globals.bracketUpdate("not an ident", 0)
Globals.bracketCall("not an ident")(0)
// Accessing a member whose JS name is a symbol
Globals.symbolVar
Globals.symbolDef()
js.Dynamic.global
is an @JSGlobalScope object
that lets you read and write any field and call any top-level function in a dynamically typed way.As with any other global-scope object, it must always be used at the left-hand-side of a dot-selection, with a valid JavaScript on the right-hand-side.
but the following uses are not valid:
val x = js.Dynamic.global
js.Dynamic.global.`not-a-valid-identifier`
val str = computeSomeString()
val y = js.Dynamic.global.selectDynamic(str)
The example means that it is not possible to dynamically look up a global variable given its name.
In Scala.js 0.6.x, it was possible to test whether a global variable exists (e.g., to perform a feature test) as follows:
if (!js.isUndefined(js.Dynamic.global.Promise)) {
// Promises are supported
} else {
// Promises are not supported
}
In Scala.js 1.x, accessing js.Dynamic.global.Promise
will throw a ReferenceError
if Promise
is not defined, so this does not work anymore.Instead, you must use js.typeOf
:
if (js.typeOf(js.Dynamic.global.Promise) != "undefined")
Just like in JavaScript, where typeof Promise
is special, so is js.typeOf(e)
if e
is a member of a global-scope object (i.e., a global variable).If the global variable does not exist, js.typeOf(e)
returns "undefined"
instead of throwing a ReferenceError
.
Dynamically lookup a global variable given its name
In general, it is not possible to dynamically lookup a global variable given its name, even in JavaScript.If absolutely necessary, one typically has to detect the global object, and use a normal field selection on it.Assuming globalObject
is a js.Dynamic
representing the global object, we can do
There is no fully standard way to detect the global object.However, most JavaScript environments fall into two categories:
- Either the global object is available as the global variable named
global
(e.g., in Node.js) - Or the “global”
this
keyword refers to the global object.
val globalObject: js.Dynamic = {
import js.Dynamic.{global => g}
if (js.typeOf(g.global) != "undefined" && (g.global.Object eq g.Object)) {
// Node.js environment detected
g.global
} else {
// In all other well-known environment, we can use the global `this`
js.special.globalThis.asInstanceOf[js.Dynamic]
}