Javascript のスコープとか参照とかクロージャとか

  • Javascript にはブロックスコープというものは無く、var による宣言は常にその関数の先頭で行われたことになる
  • 値渡しか参照渡しかは引数がオブジェクトかどうかで決まる
  • ある関数内で変数が見つからないとき、その外側の関数のスコープを再帰的に調べていく

この3つが重要な気がした。

for (var i = 0; i < 3; i++) {
  var a = i*i;
}
print(a);  // => 4
print(i);  // => 3

こんなんだから、var をローカル変数宣言だと思っていると for 文の中でぐちゃぐちゃやって確実にハマる。というかハマってた。
関数スコープはあるので、スコープを作りたいときには適当に無名関数作ってごにょごにょがセオリー?

var b = 3;
(function(){
  var b = 4;
  print(b);  // => 4
})();
print(b);  // => 3


オブジェクトのときは参照渡し、それ以外(boolean, number, string)は値渡し。

var c = 5;
(function(c){
  c = 1;
  print(c);  // => 1
})(c);
print(c);  // => 5

var x = new Array(1, 2, 3);
(function(y){
  y[0] = 99;
  print(y);  // => 99,2,3
})(x);
print(x);  // => 99,2,3


その間数内で d が見つからないため、外側まで d を探しに行って値を変更してしまう。
しかしこれのおかげでクロージャを作れる。

var d = 6;
(function(){
  d = 1;
  print(d);  // => 1
})();
print(d);  // => 1


var は常に関数の先頭で行われたことになるために、こんな動作になる。まぁ実際にはこんなコードは書かないだろうけど。

var e = 7;
var f = function(b){
  e = 2;
  print(e);  // => 2
  if (b) {
    var e = 4;  // => 4
  }
}
f(true);
print(e);  // => 7
f(false);
print(e);  // => 7
/*
全体としては
2
4
7
2
7
という出力になる
*/


Javascript1.7 からは let ってのがあって、Schemeのletっぽい使い方(let文)と、Perlのlocal っぽい使い方(let式)ができるらしいんだけど、GreaseMonkey内では使えないようだ。