Access to the JavaScript global scope in Scala.js
Unlike Scala, JavaScript has a global scope, where global variables are defined.
For example, one can define a variable foo
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:
The facade types reference explains how we can define facades for global variables. Here is a recap of the different ways:
@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
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.
Second, whereas val x = Globals.foo
translates to var x = foo
, val g = Globals
has no valid translation in JavaScript, and is a compile-time error.
Indeed, since ECMAScript 2015, there is no JavaScript value that g
could assume, such that g.foo
would evaluate to the global variable foo
(until ECMAScript 5.1, g
could have been the global object, and this is what Scala.js 0.6.x did).
In general, any “dynamic” reference to a global-scope object is a compile-time error in Scala.js 1.x.
Global-scope restrictions
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:
Only the following uses of Globals
would be valid:
All of the following uses are compile-time errors:
The case of js.Dynamic.global
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.
For example, the following uses are valid:
but the following uses are not valid:
The example means that it is not possible to dynamically look up a global variable given its name.
Practical tips
Testing whether a global variable exists
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:
In modern Scala.js, 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
:
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.
The global variable global
can of course be read with js.Dynamic.global.global
(the double global
is intended).
The global this
can be read with js.special.fileLevelThis
.
Together, these can be used to correctly detect the global scope in most environments: