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:

var foo = 42;

which then makes it available in the global scope, so that another script can read or write it:

foo = 24;

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

object Globals extends js.Object {
  var foo: Int = js.native

class Bar extends js.Object

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 = = 24

val y = new Bar
val z = Bar

would “translate” to

var x = foo;
foo = 24;

var y = new Bar();
var z = Bar;

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 = 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 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 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:

import scala.scalajs.js
import scala.scalajs.js.annotation._

object Symbols {
  val sym: js.Symbol = js.Symbol()

object Globals extends js.Any {
  var validVar: Int = js.native
  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

  def bracketSelect(name: String): Int = js.native
  def bracketUpdate(name: String, v: Int): Unit = js.native

  def bracketCall(name: String)(arg: Int): Int = js.native

  var symbolVar: Int = js.native
  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.bracketUpdate("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

// Accessing something that is not a valid JS identifier
Globals + 0

// Calling an `apply` method without `@JSName`

// Accessing a bracket-access/call member with a non-constant string
val str = computeSomeString()
Globals.bracketUpdate(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

The case of 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:

val x = = 24

but the following uses are not valid:

val x =`not-a-valid-identifier`

val str = computeSomeString()
val y =

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:

if (!js.isUndefined( {
  // Promises are supported
} else {
  // Promises are not supported

In modern Scala.js, accessing will throw a ReferenceError if Promise is not defined, so this does not work anymore. Instead, you must use js.typeOf:

if (js.typeOf( != "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

val x = globalObject.selectDynamic(dynVarName)

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 (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:

val globalObject: js.Dynamic = {
  import js.Dynamic.{global => g}
  if (js.typeOf( != "undefined" && ( eq g.Object)) {
    // Node.js environment detected
  } else {
    // In all other well-known environment, we can use the global `this`