JavaScript でブロックスコープを実現する2006年07月08日 21時22分

JavaScript には基本的にブロックスコープというものが存在しない。どうしてもブロックスコープを扱いたいときは function 式を使ったりする。

var a = 10;
{
  var a = 20;
  print(a); // 20
}
print(a); // 20
var a = 10;
(function () {
  var a = 20;
  print(a); // 20
})();
print(a); // 10

だがやはりブロックスコープがあったほうが便利ということで JavaScript 1.7 では let 式、let 文、let 宣言が導入される。

var a = 10;
let (a = 20) {
  print(a); // 20
}
print(a); // 10

しかしこれでは対応するブラウザが Firefox 2 以降などに限られる。ところが、with 文とオブジェクトリテラルを使えばブロックスコープを実現できることを Bug 343765 に添えられたテストケースで知った。

var a = 10;
with ({ a: 20 }) {
  print(a); // 20
}
print(a); // 10

これは for ループ中でクロージャに使われる変数の値を束縛したいときなどに威力を発揮する。「ma2の日記 - Functionオブジェクト」の例ならば以下のように書ける。

var f = [];
for (var i = 0; i < 10; i++)
  with ({ i: i })
    f[i] = function (a) { return a + i; };

print(f[3](4)) // 7

with 文は入れ子にできるので以下のようにも書ける。

var f = [];
with ({ i: 0 })
  for (; i < 10; i++)
    with ({ i: i })
      f[i] = function (a) { return a + i; };

print(f[3](4)); // 7
print(i); // ReferenceError: i is not defined

また、with 文を使うことで prototype.js の bind のようなこともできる。

function E(element)
{
  ...
  // prototype.js を使う場合の
  //   element.onmouseover = this.show.bind(this);
  //   element.onmouseout = this.hide.bind(this);
  // とほぼ等価。
  with (this) {
    element.onmouseover = function () { return show(); };
    element.onmouseout = function () { return hide(); };
  }
  ...
}

E.prototype.show = function () { ... };
E.prototype.hide = function () { ... };

正直 with 文にこのような使い道があろうとは目から鱗だった。関係ないが、Bug 343765 comment 13send your tachyons という表現の意味がわからない。光の速さよりも速くスーパーレビューをしてということだろうか?