scala.js 1.0 中的@JSGlobalScope(JavaScriptException、ReferenceError、var 未定义)

@JSGlobalScope in scala.js 1.0 (JavaScriptException, ReferenceError, var is not defined)

从 scala.js 0.6.x 迁移到 1.0 后,我有一些与 @JSGlobalScope 相关的代码损坏了。

我的用例是这样的:

代码如下所示:

  @js.native
  @JSGlobalScope
  object Globals extends js.Object {
    var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
  }

然后我这样设置这个变量:

Globals.callbackFunctionFor3rdPartyLib = () => {
   // do things
}

然后我将脚本添加到 DOM.

这是与 scala.js 0.6.x 一起工作的,但与 1.0 一起工作时出现如下异常:

scala.scalajs.js.JavaScriptException: ReferenceError: callbackFunctionFor3rdPartyLib is not defined

changelog for 1.0.0 中有一个 "Breaking changes" 部分提到了这一点:

Accessing a member that is not declared causes a ReferenceError to be thrown ...

js.Dynamic.global.globalVarThatDoesNotExist = 42

would previously create said global variable. In Scala.js 1.x, it also throws a ReferenceError.

我的问题是:

在 scala.js 1.0 中做这样的事情(创建一个新的全局变量)的正确方法是什么?

如果您知道自己将始终处于浏览器上下文中,则可以在 Globals 上使用 @JSGlobal("window") 而不是 @JSGlobalScope,这等同于执行 window.myGlobalVarFor3rdPartyLib 在 JS 中。这样就可以了。

@js.native
@JSGlobal("window")
object Globals extends js.Object {
  var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}

如果不是,但你使用的是 script(所以不是 CommonJS 也不是 ES 模块),最好的办法是使用

object Globals {
  @JSExportTopLevel("myGlobalVarFor3rdPartyLib")
  var foo: js.Function[Unit] = ...
}

请注意,Globals 现在是一个普通的 Scala 对象,而不是 JS 对象。

@JSExportTopLevel 在脚本的顶部创建了一个顶层 var myGlobalVarFor3rdPartyLib,然后分配 Globals.foo 也会分配那个顶层 var


如果您不使用脚本,也不知道您将始终在浏览器中,那么您需要自己找出全局对象。 Scala.js 0.6.x 尝试为您这样做,但可能会失败,所以我们不再这样做了。您至少可以按照 the documentation of js.special.fileLevelThis 上的 "instructions" 来重现 Scala.js 0.6.x 正在做的事情。我在这里重复说明:

Using this value should be rare, and mostly limited to writing code detecting what the global object is. For example, a typical detection code--in case we do not need to worry of ES modules--looks like:

val globalObject = {
  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.fileLevelThis
  }
}

Note that the above code is not comprehensive, as there can be JavaScript environments where the global object cannot be fetched neither through global nor this. If your code needs to run in such an environment, it is up to you to use an appropriate detection procedure.