グローバル変数はグローバルオブジェクトのプロパティになる
グローバルオブジェクト(the global object)は、ブラウザであればwindow
、nodejsであればglobal
といった名前でデフォルトに定義されている。そして定義されたグローバル変数を自身のプロパティとして持つという特徴がある。
"use strict";
var a;
function b(){};
//グローバルオブジェクトを取得するイディオム
console.log(Object.keys(Function(`return this`)()));
["external", "chrome", "document", "a", "b", ..., "self", "window"]
["window", "b", "a", "document", "screen", "history", "locationbar", "menubar", "personalbar", "scrollbars", …]
この場合、グローバル変数a
,b
は window.a
/window.b
と言った形で参照することができるのだ。
※なお、Nodejsでは一見グローバルなスコープに見える場所はスクリプト単位のローカルスコープなので、グローバルオブジェクト(global
)のプロパティにはならない。
let/const/class宣言の場合
ES6(ES2015)のlet,const,classは上記と異なり、仕様ではグローバルなスコープで定義してもグローバルオブジェクトのプロパティとはならない。しっかりとスコープが区切られ、外部に公開されていない。そのためグローバルオブジェクト経由で参照することもできない。
"use strict";
var a;
function b(){};
//es2015>=
let c;
const d=0;
class e{};
console.log(Object.keys(Function(`return this`)()));
["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 {}
["external", "chrome", "document", "a", "b", "f", ...
トランスパイラ/AltJSの問題
BabelやTypeScriptではES6のコードをES3/5に変換する際、let
/const
はただのvar
、class
もvar
とfunction
の組み合わせで表現される。そのため、グローバルに宣言するとvar
/function
と同じく、グローバルオブジェクトのプロパティとして追加されてしまう。
"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")()));
["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を定義するとグローバルオブジェクトのプロパティとして見えてしまう。
"use strict";
var a;
function b() { }
class e {}
console.log(Object.keys(Function(`return this`)()));
["window", "b", "a", "document", "e", "screen", "history", "locationbar", "menubar", "personalbar", …]
Safariのclassはレキシカルスコープに未対応であり、それと同根の問題であると思われる。