JavaScript 1.7 の新機能2006年08月12日 16時37分

Firefox 2 の新機能の一つに JavaScript 1.7 への対応がある。Firefox 2 Beta 2 のリリースも近づき JavaScript 1.7 の新機能もほとんどが実装されてきたのでここにまとめてみる。といっても New in JavaScript 1.7 に大体はまとまっているので補足的な部分など。

なお、ブラウザ上でスクリプトを実行する場合は、JavaScript のバージョンを明記しないと let 、yield がキーワードとして認識されない (Bug 351515) 。

<script type="text/javascript; version=1.7">

JavaScript shell を使う場合は起動時にバージョンを指定するか version 関数を使う。

$ js -v 170
js> version(170);

識別子の扱い

これまでは for 、return といった予約語を識別子 (変数名、プロパティ名など) として直接使うことはできなかったが、JavaScript 1.7 からは確実にプロパティ名だと判断できる文脈に限って予約語を直接識別子として扱うことができるようになった (Bug 336376) 。具体的な文脈は以下のとおりである。

  • オブジェクトリテラルのプロパティ名部分
  • プロパティアクセス演算子 . の直後 (ただし後に名前空間修飾演算子 :: が続く場合を除く)
  • 子孫アクセス演算子 .. の直後 (ただし後に名前空間修飾演算子 :: が続く場合を除く)
  • 属性識別演算子 @ の直後 (ただし後に名前空間修飾演算子 :: が続く場合を除く) (Bug 345922)
  • 名前空間修飾演算子 :: の直後
  • 関数定義における関数名部分 (Bug 343675)
// JavaScript 1.6 以前
var o = { "for": 42, "delete": function () { ... } };
var t = o["for"];
o["delete"]();

// JavaScript 1.7
var o = { for: 42, delete: function () { ... } };
var t = o.for;
o.delete();

ブロックスコープ

これまで JavaScript には大まかにいえば大域スコープと関数内スコープしか存在しなかったが、ブロックスコープを実現すべく let 文、let 式、let 宣言が導入された (Bug 336378) 。let 文と let 式を区別するために let 文はブロック、let 式は式しか直後に取れない。また、let 宣言は var 宣言と同じく実行に先立って処理される。なお、let 文は with 文を使って書き換え可能である。

var f = [];
// for 文中の let 宣言
for (let i = 0; i < 3; i++) {
  // let 式
  f[i] = let (x = i) function (y) { return x + y; };
}

print(f[1](3)); // 4
print(i); // ReferenceError: i is not defined
var a = 10;

// let 文
let (a = 2 * a) {
  print(a); // 20
}
print(a); // 10;

{
  print(a); // undefined
  // let 宣言
  let a = 30;
  print(a); // 30
}
print(a); // 10

分割代入

配列リテラル形式およびオブジェクトリテラル形式を使って複数の変数を一括して代入することが可能になった (Bug 336379) 。なお、配列リテラル形式の分割代入は Opera でも利用可能である。(destructuring assignment を何と訳せばいいのか思いつかないのだが brazil 氏に従って「分割代入」とした。)

// 配列リテラル形式の分割代入
var [a, b] = [10, 20];
print(a); // 10

// 変数の交換
[a, b] = [b, a];
print(a); // 20

// オブジェクトリテラル形式の分割代入
// var pi = Math.PI, e = Math.E;
var { PI: pi, E: e } = Math;
print(e); // 2.718281828459045

// 関数の引数における分割代入
[[1, 2], [3, 4], [5, 6]].map(function ([a, b]) { return a + b; });
// [3, 7, 11]

なお、for-in 文中で分割代入を使うとオブジェクトのプロパティ名とそのプロパティの値が代入される。また、分割代入中に左辺式を記述しないとエラーになる。

var obj = { a: 10, b: 20, c: 30 };
for (let [key, value] in obj)
  print(key + " = " + value);
// a = 10
// b = 20
// c = 30

var [,,] = [10, 20]; // SyntaxError: missing variable name:

イテレータ

Python からイテレータ機能が取り込まれ、オブジェクトのプロパティに順にアクセスできるようになった (Bug 326466) 。Iterator 関数を呼び出すことでイテレータオブジェクトを得ることができ、イテレータオブジェクトの next メソッドを呼び出すたびにオブジェクトのプロパティ名およびプロパティ値を納めた配列が順に返される。すべてのプロパティが返された後に next メソッドを呼び出すと StopIteration 例外が投げられる。

var obj = { a: 10, b: 20 };
var it = Iterator(obj);
print(it.next().toSource()); // ["a", 10]
print(it.next().toSource()); // ["b", 20]
it.next(); // uncaught exception: [object StopIteration]

Iterator 関数の第 2 引数に true を指定するとプロパティ名のみが列挙される。また、イテレータオブジェクトは for-in 文を使ってもアクセスできる。

var it = Iterator(obj, true);
print(it.next()); // a

var it = Iterator(obj);
for (let i in it)
  print(i);
// a,10
// b,20

Iterator 関数は内部的にそのオブジェクトの __iterator__ メソッド (通常は Object.prototype から継承する) を呼び出しているので、__iterator__ メソッドを自分で実装することにより独自のイテレータを返すことができる。

var obj = {
  __iterator__: function (keysOnly) {
    return Iterator({ a: 10, b: 20 }, keysOnly);
  }
};
var it = Iterator(obj);
print(it.next()); // a,10

ジェネレータ

Python からジェネレータ機能が取り込まれ、イテレータを簡単に作成できるようになった (Bug 326466) 。個人的な理解で言えばジェネレータとは内部イテレータを外部イテレータに変換するための仕組みである。基本的な使い方は「Latest topics > JavaScript 1.7のyield文ってなんじゃらほ - outsider reflex」を参照のこと。ジェネレータ関数を実行することでジェネレータイテレータが生成される。ジェネレータイテレータはイテレータオブジェクトと同じく for-in 文を使ってアクセスすることもできる。

function fib()
{
  var [a, b] = [0, 1];
  while (true) {
    yield b;
    [a, b] = [b, a + b];
  }
}

var f = fib();
// 100 以下のフィボナッチ数を表示
for (let i in f) {
  if (i > 100) break;
  print(i);
}

さらに JavaScript 1.7 では、Python のジェネレータに関する最新の機能も取り込み、ジェネレータをコルーチンとして扱えるようになっている。yield は文ではなく式であり、yield 式がほかの演算子のオペランドとして使われるときは yield 式全体を括弧で囲う必要がある。以下のメソッドがジェネレータイテレータを操作するために使われる。

next()
前回の終了地点 (最後に評価した yield 式の場所、初めて呼び出すときはジェネレータ関数の先頭) から実行を再開する。最後に評価した yield 式の値は undefined になる。yield 式に到達したらその yield 式が指定する値 (省略可能、省略した場合は undefined) を返して実行を中断する。return 文 (値をとることはできない) またはジェネレータ関数の終端に到達した場合は StopIteration 例外を投げる。
send(v)
前回の終了地点から実行を再開する。最後に評価した yield 式の値は v になる。引数 v を省略した場合は next() と同じ意味になる。next() または send() を呼び出さずにいきなり send(v) を呼び出すことはできない。yield 式に到達したらその yield 式が指定する値を返して実行を中断する。return 文またはジェネレータ関数の終端に到達した場合は StopIteration 例外を投げる。
throw(e)
前回の終了地点 (初めて呼び出すときはジェネレータ関数の先頭) から e を投げ実行を再開する。e がジェネレータ関数内で捕捉されなかった場合はそのまま throw() の呼び出し元に e が伝播する。yield 式に到達したらその yield 式が指定する値を返して実行を中断する。return 文またはジェネレータ関数の終端に到達した場合は StopIteration 例外を投げる。
close()
前回の終了地点 (初めて呼び出すときはジェネレータ関数の先頭) から GeneratorExit 例外を投げ実行を再開する。ジェネレータ関数内で StopIteration 例外 (return 文またはジェネレータ関数の終端に到達した場合も含む) または GeneratorExit 例外 (投げられた例外が捕捉されなかった場合も含む) が投げられた場合は呼び出し元に返る。このとき呼び出し元に例外は伝播しない。その他の例外が投げられた場合は呼び出し元に TypeError 例外が投げられる (この動作は Python のもの――投げられた例外が呼び出し元に伝播する――とは異なる) 。yield 式に到達したらその yield 式が指定する値を返して実行を中断する (この動作は Python のもの―― RuntimeError を投げる――とは異なる。これがバグなのか仕様なのかは不明) 。前回の終了地点 (初めて呼び出すときはジェネレータ関数の先頭) で return 文を実行したかのように実行を終了する (この動作は Python のもの――前回の終了地点から GeneratorExit 例外を投げる――とは異なる) 。この場合呼び出し元に StopIteration 例外は投げられない。yield 式に到達したら呼び出し元に TypeError 例外が投げられる。例外が投げられた場合はその例外が呼び出し元に伝播する。(Bug 349331)
function f()
{
  try {
    var x = 5 + (yield (yield));
    yield x;
  } catch (e) {
    yield e;
  }
}

var gen = f();
print(gen.next()); // undefined
print(gen.send("Hello!")); // Hello!
print(gen.send(3)); // 8
print(gen.throw(42)); // 42;
print(gen.next()); // uncaught exception: [object StopIteration]

配列内包

Python から配列内包が取り込まれ、イテレータやジェネレータから簡単に配列を生成できるようになった (Bug 326466) 。Python とは異なり for 節、if 節には括弧が必要である。また、for 節で用いられる変数のスコープは自動的に配列内包内となる。

var obj = { a: 10, b: 20, c: 30 };
[i.toUpperCase() + obj[i] for (i in obj)];
// ["A10", "B20", "C30"]

var range = [1, 2, 3]
[[i, j] for each (i in range) for each (j in range) if (i != j)];
// [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]

参考