徒然技術日記

Object.prototype.__noSuchMethod__

Components.utils.import でグローバル汚染を引き起こさずにインポートする方法

Firefox 本体ではおそらくだいぶ前から使われているだろうから今更感あるし、2015年にもなって Mozilla-specific な JavaScript の記事に需要があるのかはわからないけれど、先ほどとあるコードモジュールを読んでいて知見を得たので共有します。

Components.utils.import は第2引数に何も指定しない場合、コードが書かれているスコープではなく、グローバルスコープに指定したモジュールを展開します。そのため、せっかく以下のように Module Pattern を使って書いていたとしても、グローバル汚染が引き起こされます。

(function(global){
    "use strict";

    Components.utils.import("resource://my-module/Hoge.jsm");
    // do something
})((this || 0).self || global);

Hoge === undefined; // -> false

しかしながら、第2引数に名前空間を指定して、モジュールに対しては常にその名前空間を介してアクセスするというのもコードが無駄に冗長になり、つらみが高まります。

let Modules = {};
Components.utils.import("resource://my-module/Hoge.jsm", Modules);
    
Modules.Hoge.foo();
if(Modules.Hoge.bar()){
    // do something
}

そこで有効なのが、Components.utils.import がモジュールのグローバルオブジェクトを返すということを利用し、空のオブジェクトをスコープとして渡した上で、返り値をローカル変数で受ける方法です。Object destructuring を用いることでかなり簡潔に書くことができます。

let { Hoge } = Components.utils.import("resource://my-module/Hoge.jsm", {});

返り値の利用は非推奨(use of the return value is discouraged)と MDN に書かれているのが若干気になるところですが、Firefox 本体にも多く使われているテクニックなので恐らく大丈夫でしょう。

(追記 2015/5/16 18:00) id:Syoichi さまのご指摘に基づき、Object destructuring と Shorthand Properties を誤解していたことが判明したため、上記最後の部分を修正。元文章は以下の通り。

let { Hoge: Hoge } = Components.utils.import("resource://my-module/Hoge.jsm", {});

返り値の利用は非推奨(use of the return value is discouraged)と MDN に書かれているのが若干気になるところですが、Firefox 本体にも多く使われているテクニックなので恐らく大丈夫でしょう。

さらに、Firefox 33 以降であれば Shorthand Properties が使えるので、上記はより簡潔に

let { Hoge } = Components.utils.import("resource://my-module/Hoge.jsm", {});

と書くことができます。