ユーザスクリプトを (function(){ ... })(); で囲むことについて

末尾に追記アリ


自分の中では GreaseMoneky や Opera の User JavaScript を書くときに

(function(){
 // 処理を普通に書く
})();

クロージャで書いてスコープを切ることが常識だと思っていた。
野良スクリプトでこういう風に書いてないと「勉強不足なのねー」とか
思っちゃってたわけだけども、よく調べたみたら
自分が勉強不足だったことが判明したのでまとめてみる。

なぜ必要か(と思っていたか)

ユーザスクリプト内でグローバル変数を書いた場合に
対象サイトのスクリプトグローバル変数と衝突する可能性があるため。
例えば、対象サイトで config という変数を使っているときに
ユーザスクリプトでそれを知らずに config という変数を宣言したりすると
意図しない動作をする場合がある。
というかそれでハマった↓
Googleにブログ検索とコード検索を追加するGreaseMonkey - mallowlabsの備忘録

と思っていたけど

GreaseMoneky ではユーザスクリプトのスコープは
きちんと区切られるようになっている。
ちょっと前まではユーザスクリプトの前に (function(){ をいれて
後ろに })(); をくっつけるだけのアレな実装になっていたけど
今はちゃんとしているらしい。
参考:Greasemonkeyは(function(){\nと\n})()を付加しなくなる(という夢を見た) - FFFF - 0x

試してみる

test.html

<html>
<head>
<script type="text/javascript">
  var a = 1;
</script>
</head>
<body>
  <button onclick="alert(a)">click</button>
</body>
</html>


test.user.js

var a = 2;


同じスコープで a を宣言してるので alert(a) では
後から代入されている2が表示されてるものだと思っていたけど
GreaseMonekey では ちゃんと1が表示される。

a = 2;

みたいにして a をグローバル変数にしようとしてもちゃんと1が表示される。


GreaseMonekey で alert(a) の表示を2にしたい場合には以下の様にする。


test.user.js

unsafeWindow.a = 2;

Opera での挙動

今回一番問題なのは、上の話は GreaseMoneky 限定 だということ。
Opera のユーザスクリプトはスコープを分離したりしないので alert(a) は2と表示される。
もちろん unsafeWindow なんてものもない。

結論

GreaseMonekey 以外のブラウザを考慮した場合、
スクリプトを (function(){ ... })(); で囲うべき
# 例え「Opera?そんなマイナーブラウザしらねーよ」と思っていても。
少なくとも Opera ユーザには「こいつわかってるぜ」と思われる。


同様に、何も考えずに unsafeWindow を使っていると
Opera ユーザを悲しい気分にさせるので

var w = this.unsafeWindow || window;

みたいにして w を参照するようにすると Opera ユーザに喜んでもらえる。


でも Opera マニアは

var unsafeWindow = window;

と書かれたユーザスクリプトを導入してたりするので
あんまり喜ばなかったりもする。

要するに

GreaseMonkey スクリプトの開発者はもっと Opera ユーザに優しくするべき。
ではなくてユーザスクリプトは (function(){ ... })(); で囲うべき。

2009.04.11 追記

下記のように document を引数にした状態で
スクリプトを囲むべきであるというのが現在の認識。

(function(document){
 // 処理を普通に書く
})(document);

上で説明した通り Opera でスコープを切ることもでき
なおかつ Firefox でもスクリプトの高速化が見込める。
参考:2行でJavaScriptを高速化する方法 - rand's