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:

  1. console.log(foo);
  2. foo = 24;

The explains how we can define facades for global variables.Here is a recap of the different ways:

  1. @js.native
  2. @JSGlobalScope
  3. object Globals extends js.Object {
  4. var foo: Int = js.native
  5. }
  6. @js.native
  7. @JSGlobal
  8. class Bar extends js.Object
  9. @js.native
  10. @JSGlobal
  11. 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

  1. val x = Globals.foo
  2. Global.foo = 24
  3. val y = new Bar
  4. 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 or Globals.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 but Global.foobar(someVal) isn’t)
    • Otherwise, if foobar has an @JSName(jsName) then jsName must be a constant string which is a valid JavaScript identifier
    • Otherwise, foobar must be a valid JavaScript identifier different than apply

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:

  1. import scala.scalajs.js
  2. import scala.scalajs.js.annotation._
  3. object Symbols {
  4. val sym: js.Symbol = js.Symbol()
  5. }
  6. @JSGlobalScope
  7. object Globals extends js.Any {
  8. def validDef(): Int = js.native
  9. var `not-a-valid-identifier-var`: Int = js.native
  10. def `not-a-valid-identifier-def`(): Int = js.native
  11. def +(that: Int): Int = js.native
  12. def apply(x: Int): Int = js.native
  13. @JSBracketAccess
  14. def bracketSelect(name: String): Int = js.native
  15. @JSBracketAccess
  16. def bracketUpdate(name: String, v: Int): Unit = js.native
  17. @JSBracketCall
  18. def bracketCall(name: String)(arg: Int): Int = js.native
  19. @JSName(Symbols.sym)
  20. var symbolVar: Int = js.native
  21. @JSName(Symbols.sym)
  22. def symbolDef(): Int = js.native
  23. var arguments: js.Array[Any] = js.native
  24. @JSName("arguments") def arguments2(x: Int): Int = js.native
  25. }

Only the following uses of Globals would be valid:

  1. Globals.validVar
  2. Globals.validDef()
  3. Globals.bracketSelect("someConstantIdent")
  4. Globals.bracketUpdate("someConstantIdent", anyExpression)
  5. Globals.bracketCall("someConstantIdent")(anyExpression)

All of the following uses are compile-time errors:

  1. // Not used as the left-hand-side of a dot-selection
  2. val x = Globals
  3. someMethod(Globals)
  4. // Accessing something that is not a valid JS identifier
  5. Globals.`not-a-valid-identifier-var`
  6. Globals.`not-a-valid-identifier-def`()
  7. Globals + 0
  8. Globals.arguments
  9. // Calling an `apply` method without `@JSName`
  10. Globals(0)
  11. // Accessing a bracket-access/call member with a non-constant string
  12. val str = computeSomeString()
  13. Globals.bracketSelect(str)
  14. Globals.bracketCall(str)(0)
  15. // Accessing a bracket-access/call member with a non-valid JS ident
  16. Globals.bracketSelect("not an ident")
  17. Globals.bracketUpdate("not an ident", 0)
  18. Globals.bracketCall("not an ident")(0)
  19. // Accessing a member whose JS name is a symbol
  20. Globals.symbolVar
  21. 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:

  1. val x = js.Dynamic.global
  2. js.Dynamic.global.`not-a-valid-identifier`
  3. val str = computeSomeString()
  4. 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:

  1. if (!js.isUndefined(js.Dynamic.global.Promise)) {
  2. // Promises are supported
  3. } else {
  4. // Promises are not supported
  5. }

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:

  1. 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.
  1. val globalObject: js.Dynamic = {
  2. import js.Dynamic.{global => g}
  3. if (js.typeOf(g.global) != "undefined" && (g.global.Object eq g.Object)) {
  4. // Node.js environment detected
  5. g.global
  6. } else {
  7. // In all other well-known environment, we can use the global `this`
  8. js.special.globalThis.asInstanceOf[js.Dynamic]
  9. }