生きることは忘れること

JavaScriptの window と self と globalThis の話(その1)

昨日の仕様書の話の続きというか実例です。

まえおき

JavaScriptには window というオブジェクト(変数)があり、グローバル変数は window のプロパティとしてもアクセスできる、ということはある程度触ったことのある方ならご存知のことであろう。具体的には、たとえばアドレスバーのURLを操作するために使われる history.replaceState()window.history.replaceState() と書いても同じ意味になる。 window 自体もグローバル変数なので、 window.window.window.history.replaceState() と書いても動く(もちろんこんな書き方をすべきではないが)。

JavaScript関連の仕様書は相当精緻に作られている方である。Webというプラットフォームで使われるので、処理系はブラウザであり、複数の実装(要するに複数のブラウザ)がある。そしてユーザ数は非常に多く、セキュリティ上のリスクも高い。したがって実装間の互換性を確保することが極めて重要であり、そのために仕様書も精緻となっているのである。そんなわけで、この window の挙動も仕様書で定められたものである。以下ではそれについて見ていこうと思う。

その前に仲間たちを紹介しておこう。昔のJavaScriptでは window だけでよかったが、最近のブラウザにはworkerというものがある。おそらく比較的馴染み深いのはService Workerであろう。 window というのはブラウザウィンドウ(ないしタブ)と対応したインタフェースなので、バックグラウンドで動くworkerには window というオブジェクトはなく、グローバル変数は self というオブジェクト(変数)のプロパティになっている。逆に、 self という変数は普通のWebページのJavaScriptにも存在し、 window と同じオブジェクトを指すようになっている。つまり先の例は self.history.replaceState() と書いてもいいし self.self.window.window.history.replaceState() と書いてもいい。

さらにNode.jsの場合、 windowself もなく代わりに global というのがある。とてもバラバラで厄介なので、最近のJavaScriptではどんな環境でも globalThis という変数で問題のオブジェクトが参照できるようになっている。つまり globalThis.history.replaceState() と書いてもいいし、そろそろオチがわかってきているだろうが globalThis.globalThis.self.self.window.window.history.replaceState() と書いてもいい。このあたりのことは👻globalThis👻と🌏global🌏と🌝this🌝 - Qiitaに詳しいので参照されたい。

ECMAScript

さて本編に入る。まず我々がJavaScriptと呼んでいるものの仕様はECMA-262という文書で、そこではECMAScriptという名称が使われている。Ecma Internationalという団体に置かれているTC39という委員会が策定しており、 https://tc39.es/ecma262/ というURLで参照できる最新のドラフトを見ることが多い。ただし以下では引用のため版を固定すべく14th editionであるECMAScript 2023を参照する。

The Global Object

ECMAScriptの19章が“The Global Object”という章で、冒頭部を引用すると以下のようになる。

The global object:

ここで2度ほど登場する“host-defined”という語が一つの鍵となる概念であり、原文では4.2節“Hosts and Implementations”内への内部リンクが貼られている。噛み砕いて言うと、ECMAScriptの仕様ではWebブラウザのすべての機能を定義しているわけではなく、汎用的なプログラミング言語としてのECMAScript (JavaScript) を定義した上で、個々の応用先ごとに必要となる機能は別の仕様によって拡張するという形になっている。“host”とはブラウザであり、JavaScriptのうちWebに関連する機能を定義する仕様(後出のHTML仕様など)のことである。

To aid integrating ECMAScript into host environments, this specification defers the definition of certain facilities (e.g., abstract operations), either in whole or in part, to a source outside of this specification.

私訳:ECMAScriptとホスト環境の統合に資するため、この仕様はいくつかの機能性(例:抽象演算)の定義を、その全体または一部において、この仕様の外部に委譲する。

In informal use, a host refers to the set of all implementations, such as the set of all web browsers, that interface with this specification in the same way via Annex D. A host is often an external specification, such as WHATWG HTML (https://html.spec.whatwg.org/). In other words, facilities that are host-defined are often further defined in external specifications.

私訳:インフォーマルな用法としては、「ホスト」とは、付録Dで定義されるこの仕様の拡張を同じ仕方で行う「実装」すべての集合、たとえばWebブラウザすべての集合を指す。ホストはしばしば外部の仕様であり、一例としてはWHATWG HTMLがそうである。言い換えれば、「ホスト定義」な機能性はしばしば、外部の仕様においてさらに定義されている。

だいぶ長くなったが話を戻すと、the global objectについてであった。host-definedが登場するうちの箇条書きの最後の項目を読むと、グローバル変数や関数をホストが独自に定義してよく、それは値がthe global object自身であるものでもかまわないと書いてある。このあたりが今回の話と関係していそうであるが、とりあえずいまのところは伏線である。

globalThis

globalThis19.1.1節で定義されている。

The initial value of the "globalThis" property of the global object in a Realm Record realm is realm.[[GlobalEnv]].[[GlobalThisValue]].

難しいが(原文のマークアップが引用では損なわれてしまっているので尚更)、これは「 realm というRealm Record内のglobal objectの"globalThis"プロパティのinitial valueは realm.[[GlobalEnv]].[[GlobalThisValue]] である」と構文解析する。 .[[GlobalEnv]] という記法は内部スロットと呼ばれるもので、仕様書だけに存在する(実際のプログラムからは使えない)仮想的なプロパティのようなものである。realmとかinitial valueが何かを説明し始めると話が膨らみすぎるので割愛するが、内部リンクを踏みつつ9.3節“Realms”9.1.1.4節“Global Environment Records”と辿ると、問題の realm.[[GlobalEnv]].[[GlobalThisValue]]

The value returned by this in global scope. Hosts may provide any ECMAScript Object value.

なのだそうだ。第2文はさておき第1文に着目し、 this の定義から13.2.1節“The this Keyword”9.4.4節“ResolveThisBinding()9.4.3節“GetThisEnvironment()9.1節“Environment Records”と辿っていくと、いわゆる this のスコープという概念にはenvironment recordというオブジェクトが絡んでいることが分かる。global scopeには参照リンクがないので意味が不明瞭なのだが、最も外側のことだと思っておけばいいだろう。environment recordにはいくつか種類(サブクラス)があるが、9.1.1.4節“Global Environment Records”というのがどうやらそれのようだ。

A Global Environment Record provides the bindings for built-in globals (clause 19), properties of the global object, and for all top-level declarations (8.2.9, 8.2.11) that occur within a Script.

というわけで、スタート地点のthe global objectに戻ってくることが分かる。またトップレベルの宣言(要はグローバル変数)がこのオブジェクトのプロパティになることもここで説明されているようだ。

今日はここまで。次回、HTML Standardへ。