Edited at

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

More than 3 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はレキシカルスコープに未対応であり、それと同根の問題であると思われる。