JavaScript
ECMAScript

ES6(ES2015)でグローバルに定義したlet,const,classはグローバルオブジェクトのプロパティにならない

More than 2 years have passed since last update.

グローバル変数はグローバルオブジェクトのプロパティになる

グローバルオブジェクト(the global object)は、ブラウザであればwindow、nodejsであればglobalといった名前でデフォルトに定義されている。そして定義されたグローバル変数を自身のプロパティとして持つという特徴がある。

グローバルオブジェクトのプロパティ一覧
"use strict";

var a;
function b(){};

//グローバルオブジェクトを取得するイディオム
console.log(Object.keys(Function(`return this`)()));
Chrome_v46.0.2490.86(64-bit)
["external", "chrome", "document", "a", "b", ..., "self", "window"]
Safari_v9.0(9537.86.1.56.2)
["window", "b", "a", "document", "screen", "history", "locationbar", "menubar", "personalbar", "scrollbars", ]

この場合、グローバル変数a,bwindow.a/window.bと言った形で参照することができるのだ。

※なお、Nodejsでは一見グローバルなスコープに見える場所はスクリプト単位のローカルスコープなので、グローバルオブジェクト(global)のプロパティにはならない。

let/const/class宣言の場合

ES6(ES2015)のlet,const,classは上記と異なり、仕様ではグローバルなスコープで定義してもグローバルオブジェクトのプロパティとはならない。しっかりとスコープが区切られ、外部に公開されていない。そのためグローバルオブジェクト経由で参照することもできない。

let/const/classはプロパティとなるか?
"use strict";

var a;
function b(){};
//es2015>=
let c;
const d=0;
class e{};


console.log(Object.keys(Function(`return this`)()));
ならない@Chrome_v46.0.2490.86(64-bit)
["external", "chrome", "document", "a", "b", "speechSynthesis", "caches", "localStorage", "sessionStorage", "webkitStorageInfo", "indexedDB", "webkitIndexedDB", "crypto", "applicationCache", "performance", "styleMedia", "defaultstatus", "defaultStatus", "screenTop", "screenLeft", "clientInformation", "console", "devicePixelRatio", "outerHeight", "outerWidth", "screenY", "screenX", "pageYOffset", "scrollY", "pageXOffset", "scrollX", "innerHeight", "innerWidth", "screen", "navigator", "frameElement", "parent", "opener", "top", "length", "frames", "closed", "status", "toolbar", "statusbar", "scrollbars", "personalbar", "menubar", "locationbar", "history", "location", "name", "self", "window"]

参考:Variables and scoping in ECMAScript 6

なお、以下のようなクラス式で表現する場合は、varで宣言されたグローバル変数に代入するので、変数名でプロパティ化される。

var f = class {}
Chrome_v46.0.2490.86(64-bit)
["external", "chrome", "document", "a", "b", "f", ...

トランスパイラ/AltJSの問題

BabelやTypeScriptではES6のコードをES3/5に変換する際、let/constはただのvarclassvarfunctionの組み合わせで表現される。そのため、グローバルに宣言するとvar/functionと同じく、グローバルオブジェクトのプロパティとして追加されてしまう。

typescriptでes5に変換した例
"use strict";
var a;
function b() { }
;
//es2015>=
var c;
var d = 0;
var e = (function () {
    function e() {
    }
    return e;
})();
;
console.log(Object.keys(Function("return this")()));
当然、c/d/eもプロパティになる
["external", "chrome", "document", "a", "b", "c", "d", "e", "speechSynthesis", "caches", "localStorage", "sessionStorage", "webkitStorageInfo", "indexedDB", "webkitIndexedDB", "crypto", "applicationCache", "performance", "styleMedia", "defaultstatus", "defaultStatus", "screenTop", "screenLeft", "clientInformation", "console", "devicePixelRatio", "outerHeight", "outerWidth", "screenY", "screenX", "pageYOffset", "scrollY", "pageXOffset", "scrollX", "innerHeight", "innerWidth", "screen", "navigator", "frameElement", "parent", "opener", "top", "length", "frames", "closed", "status", "toolbar", "statusbar", "scrollbars", "personalbar", "menubar", "locationbar", "history", "location", "name", "self", "window"]

この非互換性には注意する必要があるだろう。

Safariの問題

現時点のSafari(ver.9)はclass宣言に対応している(let/constは未対応)が、classを定義するとグローバルオブジェクトのプロパティとして見えてしまう。

let/constをのぞくと動く
"use strict";
var a;
function b() { }
class e {}
console.log(Object.keys(Function(`return this`)()));
Safari_v9.0(9537.86.1.56.2)
["window", "b", "a", "document", "e", "screen", "history", "locationbar", "menubar", "personalbar", ] 

Safariのclassはレキシカルスコープに未対応であり、それと同根の問題であると思われる。