Node.jsのJavaScriptはどういうコンテキストで動く?

Node.jsはまだモジュールのコードが安定していないので、モジュールのバージョンが変わるといろいろなものが動かなくなる。仕方ないのでデバッグするわけだが、thisとかグローバルオブジェクトが何かわからなくなってくるので調べておくことにした。

まず、ドキュメントGlobal Objects Node.js v0.10.29 Manual & Documentation:からわかる範囲ではglobalってモジュールがあってこれがいかにもグローバルオブジェクトっぽい。ブラウザではwindowがグローバルオブジェクトだがNode.jsではwindowは存在しない。以下のコードのように、globalにプロパティーを設定するとそれが、そのままスコープに見える。スコープのどこかにglobalがあることがわかる。

global.myVariable1 = "hey1";
console.log(global.myVariable1); // -> "hey1"
console.log(myVariable1); // -> "hey1"

標準的なObject関数も存在する。

Object.prototype.myVariable2 = "hey2";
console.log(global.Object.getPrototypeOf({})); // -> { myVariable2: 'hey2' }

global.Objectを消せば、Objectがスコープから消えるのでglobalはグローバルオブジェクトと思って間違いないだろう。

delete global.Object;
console.log(typeof Object); // undefined

次に変数定義であるが、Global Objects Node.js v0.10.29 Manual & Documentation:

In browsers, the top-level scope is the global scope. That means that in browsers if you’re in the global scope var something will define a global variable. In Node this is different. The top-level scope is not the global scope; var something inside a Node module will be local to that module.

と記述がある。適当なブラウザでF12キーを押してコンソールと表示した上で試すと、

<script>
var myVariable3 = "hey3";
console.log(window.myVariable3); // -> "hey3"
</script>

となり、Node.jsでは書いてある通り

var myVariable3 = "hey3";
console.log(global.myVariable3); // -> undefined

となる。また、ECMAScriptの仕様ECMA-262の10.4.3の

2. Else if thisArg is null or undefined, set the ThisBinding to the global object.

より、リファレンスなしの関数の呼び出しではthisはグローバルオブジェクトになるので、それも試してみる。

var myVariable4 = (function () { return this; })();
console.log(myVariable4 === global); // -> true

次にthisであるが、ブラウザでは

<script>
console.log(this === window); // -> true
console.log(this.Object); // -> function Object()
</script>

だがNode.jsでは

console.log(this === global); // -> false
console.log(this.Object); // -> undefined

のようにthisはグローバルオブジェクトではない。

Object.prototype.myVariable5 = "hey5";
console.log(Object.keys(this)); // -> []
console.log(Object.getPrototypeOf(this)); // -> { myVariable5: 'hey5' }
console.log(Object.getPrototypeOf(this) === Object.prototype); // -> true

なにか空のオブジェクトのようだ。特にスコープと関連しているわけでもない。

var myVariable6 = "hey6";
console.log(this.myVariable6); // -> undefined

require()で以下を

var myVariable7 = "hey7";
this.myVariable8 = "hey8";
myVariable9 = "hey9";
console.log(this.myVariable7); // -> undefined
module.exports = {}

以下の様にインポートした場合、

require('./globaltest3.js');
console.log(Object.keys(this)); // -> []
console.log(myVariable9); // -> "hey9"
console.log(global.myVariable9); // -> "hey9"
console.log(typeof myVariable7); // -> []

のようになるので、thisやスコープは共有されていないことがわかる。変数宣言varをつけないと、ECMA-262の8.7.2の

b. Call the [[Put]] internal method of the global object, passing GetReferencedName(V) for the property name, W for the value, and false for the Throw flag.

より、グローバルオブジェクトのプロパティが変更される。

Node.jsはvmモジュールで読み込んだファイルをサンドボックス内で実行する。以下の様にvm.runInNewContext()を使えば、実行したコードがグローバルオブジェクトを変更したとしても外部には影響しない。

var vm = require('vm');
var sandbox = {};
vm.runInNewContext("Object.myVariable10 = 'hey10'; myVariable11 = [ 'hey11' ];", sandbox);
console.log(Object.myVariable10); // -> undefined
console.log(sandbox.myVariable11); // -> [ 'hey11' ]
console.log([ 'hey11' ].__proto__ === Array.prototype); // -> true
console.log(sandbox.myVariable11.__proto__ === Array.prototype); // -> false

ちゃんとサンドボックス内でのArrayオブジェクトと外側のArrayオブジェクトを区別している。これを理解していないと

var sandbox = { test: function (str) { console.log(str); } };
vm.runInNewContext("test('hey'); test.__proto__.bind = function () { throw 'boo'; };", sandbox);
function test2() {
}
try {
    test2.bind(this);
} catch (e) {
    console.log(e); // -> boo
}

のように外の環境を破壊されてしまう。

広告

About kiidax
元、環境非依存な職業プログラマー。Blenderで遊んでいます。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。