ES6 moduleのtop levelにある`this`の値は何になるのか?
知りたい事
- ES6 moduleのtop levelにある
thisの値は何になるのか? - Babelで top level this が undefinedになって困った件 - console.lealog();
// module.js
console.log(this);// ???
Babel 前提
- Babelは入力されたコードをES6 moduleとして扱う
Babel assumes that all input code is an ES6 module
(結論的には--blacklist strictでこの挙動は無効化できる)
ES6 moduleの前提知識
- Environment Recordというのはそのスコープと変数を関連付けたりするような環境情報的なものを入れる場所
- 関数とかモジュールとかwith用みたいな何種類かある
- ModuleはModule Environment Recordsのを作ってそこに情報を記録する
- Module Environment Recordsはdeclarative Environment Recordの一種で、加えてModule専用の記録領域がある(importのbindingのための場所)
- 基本的な仕組みはdeclarative Environment Recordと同じ
(Recordの所は実装してみないとイマイチピンと来ないので大分ぼやかしてます。 Javaの実装見るとModuleEnvironmentRecordはDeclarativeEnvironmentRecordを継承するみたいなイメージでも良さそう)
ES6 thisの前提知識
thisという値がどう解決されるか
のアルゴリズムによって決定される。
2つをまぜると以下のような感じの構造
- Let envRec be GetThisEnvironment( ).
- Let lex be the running execution context’s LexicalEnvironment. // <= 今いるEnvironment(スコープみたいなもの)と取り出す
- Repeat
- Let envRec be lex’s EnvironmentRecord.
- Let exists be envRec.HasThisBinding().// <= そのEnvironment RecordsのHasThisBinding()を見る
- If exists is true, return envRec. // <= trueを返してきたら*へ
- Let outer be the value of lex’s outer environment reference.
- Let lex be outer.(Repeatへ戻る)
- Return envRec.GetThisBinding(). // *<= そのEnvironment RecordsのGetThisBinding()が
thisの値になる
擬似コード
function GetThisEnvironment(){
var lex = currentLexicalEnvironment;
while(true){
var envRec = lex.EnvironmentRecord;
if(envRec.HasThisBinding()){
return envRec;
}
lex = envRec.outer;
// 最後はglobalがlexに入る
}
}
var envRec = GetThisEnvironment();
envRec.GetThisBinding(); // thisの値
簡単にいえば、そのスコープがHasThisBinding()でtrueを返すならGetThisBinding();の返り値がthisの値になる。
Moduleのthis
Moduleはどこで作られるかというのは8.1.2.6 NewModuleEnvironment (E)で定義されている。
この内部メソッドを呼び出してるのは、15.2.1.16.4 ModuleDeclarationInstantiation( ) Concrete Methodしかないので、
NewModuleEnvironment(realm.[[globalEnv]])
先ほどのmodule Environment Recordのouterにはglobal enviromentが入る。(prototypeチェーンみたいな話)
ModuleのGetThisEnvironment
先ほど書いたように、thisの値は、そのEnvironment(スコープ)のenv.GetThisBinding()とenv.HasThisBinding()で決まる。
moduleのtop levelのコードから直近のEnvironmentはmodule Environmentなので、module Environmentの定義をみる。
moduleEnvRecord.HasThisBinding()がtrueを返すなら、moduleEnvRecord.GetThisBinding()がthisの値になると言える。
8.1.1.5 Module Environment Recordsで
に書いてあるように、moduleEnvRecordのHasThisBinding()はtrueを返す。
つまり、moduleEnvRecordのGetThisBinding()がthisの値になるので、moduleのthisはundefeinedになる。
という結論
8.1.1.5.3 HasThisBinding ()
Module Environment Records provide a this binding.
Return true.
8.1.1.5.4 GetThisBinding ()
Return undefined.
結論
BabelがコードをES6 moduleとして扱うなら、top levelのthisがundefinedになるのは仕様。
Babelにissueを立てるならば、なぜ全てをES6 moduleとして扱うのか?が焦点。
また、この挙動は--blacklist strictで無効化するオプションが用意されている。
$ babel --blacklist strict script.js
であってると思うのだけれど、間違っているなら修正したいのでIssuesかPull Requestsお願いします。
おまけ
ES6 DraftはRev 38もあるぐらい結構更新されてFinal Draftが今でてる感じです。 Moduleは特に色々変更があったと思うので、diffからこれが何時入ったのか見てみます。
答え合わせ
ES6 final tweaks #6: at the top level of a module, the this binding evaluates to undefined.
— Allen Wirfs-Brock (@awbjs) November 21, 2014
The this binding at the top level of a module has the value undefined.
December 6, 2014 Draft Rev 29の変更点
diffがハイライトされたpdf with rev29 change markupで確認できる。

テキストDiffを確認
テキストベースでES6 Draftの各Rev毎をGitのコミットにして検索できる仕組みを作りました。
git clone https://github.com/meta-ecmascript/es6-draft-revision.git
cd es6-draft-revision
git show rev29 | grep "return undefined" -i -C 10 | grep "CreateImportBinding" -B 14
output:
--
--
deletable.
+HasThisBinding ()
+
+Module Environment Records provide a THIS binding.
+
+1. Return TRUE.
+
+GetThisBinding ()
+
+1. Return UNDEFINED.
+
CreateImportBinding (N, M, N2)
--
The concrete Environment Record method CreateImportBinding for module
git logから検索
コミットの差分から検索したかったけど、何か上手くマッチしない…
git log --perl-regexp -i -G "GetThisBinding(.|\n)*?Return UNDEFINED"
--perl-regexpでperlの正規表現使えた方がいいので、gitを入れるときにbrew install git --with-pcreで入れてる。
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。