--- title: JavaScript 1.7 の新機能 slug: Web/JavaScript/New_in_JavaScript/1.7 tags: - JavaScript - JavaScript_version_overviews translation_of: Archive/Web/JavaScript/New_in_JavaScript/1.7 ---
JavaScript 1.7 はいくつかの新機能、特に{{ 原語併記("ジェネレータ", "generators") }}、{{ 原語併記("イテレータ", "iterators") }}、{{ 原語併記("配列内包", "array comprehensions") }}、{{ 原語併記("let 式", "let expressions") }}、および{{ 原語併記("分割代入", "destructuring assignment") }} を取り入れた言語アップデートです。JavaScript 1.6 のすべての機能も含まれています。
JavaScript 1.7 のサポートは Firefox 2 に導入されました。
この記事に含まれるコードの例は JavaScript シェルから実験できます。Introduction to the JavaScript shell を読み、シェルのビルド方法と使い方を学んでください。
JavaScript 1.7 の一部の新機能を使うためには、JavaScript 1.7 が使いたいという宣言が必要です。HTML または XUL コードでは次のコードを使ってください。
<script type="application/javascript;version=1.7"/>
JavaScript シェルを使うときは、コマンドラインで -version 170 スイッチを使うか、version() 関数を使って使いたいバージョンを設定する必要があります。
version(170);
新しいキーワード "yield" と "let" を使用する必要のある機能は、既存のコードでそれらのキーワードが変数や関数として利用されている恐れがあるため、バージョン 1.7 と宣言しなければ利用できません。新しいキーワードを導入していない機能 (分割代入と配列内包) は、JavaScript のバージョン宣言なしに利用できます。
{{ 原語併記("繰り返しの", "iterative") }} アルゴリズム (リストの各要素に同じ処理をしたり、同じデータセットに繰り返し計算を行うなど) を含むコードを開発する際、しばしば計算処理の間その値が維持される必要のある状態変数が使われます。伝統的には、繰り返しのアルゴリズムの介在変数を得るにはコールバック関数を使わなくてはなりません。
フィボナッチ数を計算するこの繰り返しアルゴリズムについて考えてみましょう:
function do_callback(num) {
document.write(num + "<br>\n");
}
function fib() {
var i = 0, j = 1, n = 0;
while (n < 10) {
do_callback(i);
var t = i;
i = j;
j += t;
n++;
}
}
fib();
このコードはアルゴリズムのそれぞれの繰り返しステップの処理を実行するのにコールバックルーチンを使っています。この場合、それぞれのフィボナッチ数は単純にコンソールに出力されます。
{{ 原語併記("ジェネレータ", "generators") }} および{{ 原語併記("イテレータ", "iterators") }} は相互に働き、新しく、より良くこれを実行する方法を提供します。ジェネレータを使って書かれたフィボナッチ数ルーチンがどうなっているか見てみましょう:
function fib() {
var i = 0, j = 1;
while (true) {
yield i;
var t = i;
i = j;
j += t;
}
}
var g = fib();
for (var i = 0; i < 10; i++) {
document.write(g.next() + "<br>\n");
}
yield キーワードを含む関数がジェネレータです。これを呼ぶと、ジェネレータの仮引数は実引数と結び付きますが、本体は実際には評価されません。代わりにジェネレータ・イテレータが返ってきます。ジェネレータ・イテレータの next() メソッドを呼び出すたびに、繰り返しのアルゴリズムが 1 回ずつ実行されます。それぞれのステップでの値は、yield キーワードで指定された値です。yield をアルゴリズムの繰り返しの範囲を示すジェネレータ・イテレータ版の return だと考えましょう。毎回 next() を呼び出すたび、ジェネレータのコードは yield の次の文から再開します。
あなたはジェネレータ・イテレータを、その next() メソッドを繰り返し呼び出すことで、あなたが望んだ結果の状態にたどりつくまで反復させられます。この例では、私たちが欲しいだけの結果を手に入れるまで g.next() を呼び出し続けることで、私たちはどれだけでも多くのフィボナッチ数を得ることができます。
一度 next() メソッドを呼び出してジェネレータをスタートさせると、与えた特定の値を最後の yield の結果として扱わせる send() を使うことができます。その際ジェネレータはその次の yield のオペランドを返します。
ジェネレータを勝手な時点から始めることはできません。特定の値を send() する前に必ず next() でジェネレータをスタートさせなければなりません。
send(undefined) を呼び出すことは next() を呼び出すことと等価です。しかし send() を呼び出す際、生まれたてのジェネレータを undefined 以外の値からスタートさせようとすると TypeError 例外を引き起こします。投げさせたい例外の値を渡して throw() メソッドを呼び出すことで、ジェネレータに強制的に例外を投げさせることができます。この例外はその時点の中断されたジェネレータの文脈から、つまりあたかもその時点で一時停止されている yield が throw value に置き換わったかのように投げられます。
もし投げられた例外の処理中に yield に遭遇しなかった場合、その例外は throw() の呼び出し元に伝播し、それ以降 next() を呼び出すと StopIteration が投げられます。
ジェネレータは自分自身を閉じさせる close() メソッドを持っています。ジェネレータを閉じることの効果は:
finally 節が実行されます。finally 節が StopIteration 以外の例外を投げた場合、その例外は close() メソッドの呼び出し元に伝播されます。このコードは 100 回ループするごとに yield するジェネレータを走らせます。
var gen = generator();
function driveGenerator() {
if (gen.next()) {
window.setTimeout(driveGenerator, 0);
} else {
gen.close();
}
}
function generator() {
while (i < something) {
/** 何か **/
++i;
/** 100 周ごとに yield **/
if ((i % 100) == 0) {
yield true;
}
}
yield false;
}
{{ 原語併記("イテレータ", "iterator") }} とは、データへの繰り返しの処理をしやすくする特別なオブジェクトのことです。
普通の使い方では、イテレータオブジェクトは「目に見えません」。つまりあなたはイテレータオブジェクトを明示的に操作する必要はなく、代わりに JavaScript の for...in や for each...in 文を使うことで、オブジェクトのキーや値への繰り返し処理を自然と行うことができます。
var objectWithIterator = getObjectSomehow();
for (var i in objectWithIterator) {
document.write(objectWithIterator[i] + "<br>\n");
}
もし独自のイテレータオブジェクトを実装したり、イテレータを直接操作する何か別の必要があったりするならば、あなたは next メソッドと StopIteration 例外、そして __iterator__ プロパティについて知る必要があります。
あなたは Iterator(objectname) を呼び出すことで、あるオブジェクトのイテレータを生成することができますが、そのようなあるオブジェクトのイテレータは、そのオブジェクトの __iterator__ メソッドを呼び出すことで見つけられます。もし __iterator__ が存在しなければ、デフォルトのイテレータが生成されます。デフォルトのイテレータは、普通の for...in や for each...in のモデルに基づいて、オブジェクトのプロパティを yield します。もしあなたがカスタマイズしたイテレータを提供したいならば、__iterator__ メソッドをあなたのカスタマイズしたイテレータのインスタンスを返すように上書きしてください。スクリプトからオブジェクトのイテレータを得るには、直接 __iterator__ プロパティにアクセスせず Iterator(obj) を使ってください。後者は配列 (Array) に対しても使えますが、前者は使えません。
一度イテレータを手に入れれば、そのイテレータの next() メソッドを呼び出すことで簡単にオブジェクトの次の項目を取得することができます。もしデータが残っていない場合は、StopIteration 例外が投げられます。
ここに直接的なイテレータ操作の単純な例を示します:
var obj = {name:"Jack Bauer", username:"JackB", id:12345, agency:"CTU", region:"Los Angeles"};
var it = Iterator(obj);
try {
while (true) {
print(it.next() + "\n");
}
} catch (err if err instanceof StopIteration) {
print("レコードの終わり。\n");
} catch (err) {
print("不明なエラー: " + err.description + "\n");
}
このプログラムの出力は次のようになります:
name,Jack Bauer username,JackB id,12345 agency,CTU region,Los Angeles レコードの終わり。
イテレータを生成する際、オプションとして 2 つ目の引数を指定することができます。この引数は真偽値で、next() メソッドを呼び出すごとにキーの方だけを返してほしいかどうかを示します。このパラメータはユーザー定義の__iterator__ 関数に唯一の引数として渡されます。上のサンプルで var it = Iterator(obj); を var it = Iterator(obj, true); に変えると、以下のような出力になります:
name username id agency region レコードの終わり。
どちらの場合でも、データが返ってくる実際の順番はその実装によって変わります。データの順番は無保証です。
イテレータは、その中にあなたが気づいていないデータが含まれているかもしれないオブジェクトも含め、オブジェクト中のデータをスキャンする手軽な方法です。これは特に、アプリケーションが予想していないデータを保存する必要がある場合に便利です。
{{ 原語併記("配列内包", "array comprehensions") }} は、配列のパワフルな初期化を実行する簡便な方法を提供するジェネレータの使い方です。例えば:
function range(begin, end) {
for (let i = begin; i < end; ++i) {
yield i;
}
}
range() は begin から end までのすべての数値を返すジェネレータです。このように定義すると、私たちはこれを次のように使うことができます:
var ten_squares = [i * i for each (i in range(0, 10))];
これは新しい配列 ten_squares を、0..9 の範囲にある値の平方を含むようあらかじめ初期化します。
あなたは配列を初期化する際、任意の条件文を使うことができます。もしある配列を 0 と 20 の間にある偶数が含まれるように初期化したいならば、次のコードを使うことができます:
var evens = [i for each (i in range(0, 21)) if (i % 2 == 0)];
JavaScript 1.7 以前では、これは次のようにコーディングしなければならないでしょう:
var evens = [];
for (var i = 0; i <= 20; i++) {
if (i % 2 == 0)
evens.push(i);
}
配列内包はより一層コンパクトであるだけでなく、一度そのコンセプトに精通してしまえば、実際のところ読みやすいものです。
配列内包は暗黙的な let 宣言と同様に、角カッコの内側にあるすべてを含んだその周りに暗黙的なブロックを持ちます。
Add details.
let を使ったブロックスコープデータや関数のブロックスコープを管理する let を使うにはいくつかの方法があります:
let 文 は、あるブロックのスコープ内で、ブロックの外にある同じ名前の変数の値に影響を与えることなく、変数と値を関連付ける手段を提供します。let 式 は、1 つの式だけに対して変数スコープを確立することができます。let 定義 は、その定義が行われたブロックにスコープが拘束された変数を定義します。この構文は var に対し使われる構文に非常によく似ています。let を使って for ループの文脈内でのみ存在しうる変数を確立することができます。let 文let 文は変数に対するローカルスコープを提供します。let 文はコードのある 1 つのブロックのレキシカルスコープに 0 以上の変数を結びつけることによって働き、それ以外はブロック文と全く同じです。特に、let 文の内側で var を使って定義された変数のスコープは、let 文の外側でそれが定義された場合と同じであり、そのような変数は従来通り関数スコープを持つことに注意してください。
例えば:
var x = 5;
var y = 0;
let ( x = x + 10, y = 12 ) {
print(x+y + "\n");
}
print( (x + y) + "\n" );
このプログラムからの出力は次のようになるでしょう:
27 5
コードブロックに関するルールは JavaScript の他のコードブロックと同じです。let 宣言を使って確立されたブロック自身のローカル変数を持っているかもしれません。
let 文の構文を使う時、let の後の丸カッコは必須です。これを入れないと構文エラーとなります。let を使って定義された変数のスコープは、let ブロック自身とその内部に含まれるすべてのブロックです。ただしそれらのブロックが同じ名前で変数を定義している場合を除きます。
let 式let を使ってある 1 つの式だけに対してスコープを持つ変数を確立することができます:
var x = 5; var y = 0; document.write( let(x = x + 10, y = 12) x + y + "<br>\n"); document.write( x + y + "<br>\n" );
結果として出力されるのは:
27 5
この場合、変数 x、y のそれぞれ x+10、12 との結び付きは、式 x+y に対してのみスコープを持ちます。
以下の let 式があったとすると:
let (decls) expr
expr の周りに暗黙的なブロックが生成されます。
let 定義let キーワードはブロック内で変数を定義するのにも使うことができます。
let 定義の使用例を知っているならば、どうぞここに追加することを検討してみてください。if (x > y) {
let gamma = 12.7 + y;
i = gamma * x;
}
let 文・式・定義はしばしば、内部関数が使われる際にコードを簡潔にさせることがあります。
var list = document.getElementById("list");
for (var i = 1; i <= 5; i++) {
var item = document.createElement("LI");
item.appendChild( document.createTextNode("Item " + i) );
let j = i;
item.onclick = function (ev) {
alert("Item " + j + " is clicked.");
};
list.appendChild(item);
}
上の例は、内部の無名関数の 5 つのインスタンスがそれぞれ変数 j の異なる 5 つのインスタンスを参照しているために、意図通りに動きます。もしこれで let を var に置き換えたり、変数 j を削除して単純に i を内部関数で使うと、これは意図通りには動かないことに注意してください。
let によって宣言された変数は、その定義があったブロックと、その変数が再定義されていないすべてのサブブロックにスコープを持ちます。この場合、let は var に非常によく似た働きをします。おもな違いは var 変数のスコープがそれを囲む関数全体であることです:
function varTest() {
var x = 31;
if (true) {
var x = 71; // 同じ変数!
alert(x); // 71
}
alert(x); // 71
}
function letTest() {
let x = 31;
if (true) {
let x = 71; // 違う変数
alert(x); // 71
}
alert(x); // 31
}
= の右辺の式はブロックの内側になります。これは let 式や let 文のスコープの仕方と異なります:
function letTests() {
let x = 10;
// let 文
let (x = x + 20) {
alert(x); // 30
}
// let 式
alert(let (x = x + 20) x); // 30
// let 定義
{
let x = x + 20; // ここでの x は undefined と評価される
alert(x); // undefined + 20 ==> NaN
}
}
プログラムやクラス内では、let は var がするようにグローバルオブジェクトのプロパティを生成したりはせず、代わりにその文脈で文を評価する際に生成される暗黙的なブロックにプロパティを生成します。これが本質的に意味するのは、let はそれ以前に var を使って定義された変数を上書きできないということです。例えば:
// FF 2.0 b1 ではうまく動きません。"global" ではなく "42" を返してしまいます。 var x = 'global'; let x = 42; document.write( this.x + "<br>\n" );
このコードによって表示される出力は "42" ではなく、"global" です。
{{ 原語併記("暗黙的なブロック", "implicit block") }} とは、波カッコで囲まれていないブロックのことで、JavaScript エンジンによって暗黙的に生成されます。
関数内で eval() によって実行された let は、var がするように variable object (activation object or innermost binding rib) にプロパティを生成しません。その代りに、そのプログラムで文を評価する際に生成される暗黙的なブロックにプロパティを生成します。これは eval() がプログラムに作用する性質と前述のルールによる結果です。
別の言い方をすれば、コードを実行するのに eval() を使う際、そのコードは独立したプログラムとみなされ、そのコードの周りに暗黙的なブロックを持つのです。
for ループ中での let スコープ変数let キーワードは単に var でやるようにして、for ループのスコープ内で局所的に変数を結びつけることにも使うことができます。
// obj を追加する
var i = 0;
for ( let i = i; i < 10; i++ )
document.write(i + "<br>\n");
for ( let [name,value] in obj )
document.write("名前: " + name + ", 値: " + value + "<br>\n");
for (let expr1; expr2; expr3) statement
この例で、expr2、expr3 と statement は、let expr1 によって宣言されたブロックローカルな変数を含む暗黙的なブロックに囲まれます。これは上の 1 つ目のループで実証しています。
for (let expr1 in expr2) statement for each(let expr1 in expr2) statement
これら両方の場合では、それぞれ statement を含む暗黙的なブロックができます。1 つ目の方は上の 2 つ目のループで示しています。
{{ 原語併記("分割代入", "destructuring assignment") }} は、配列やオブジェクトのリテラルの構造とそっくりの構文を使って、配列やオブジェクトからデータを抽出することを可能にします。
配列やオブジェクトのリテラル式は、データのアドホックな (その場限りの) 詰め合わせを作る簡単な方法を提供します。一度そのようなデータの詰め合わせを作ってしまえば、あなたはそれを使いたいように使うことができます。関数から返すことさえできます。
分割代入を使うと、次の節の例で示すようなさまざまな興味深いことができるようになりますが、特に便利なのは、一つの文によって全体の構造を読み込むことができるという点です。
この能力は Perl や Python などの言語に存在する機能に似ています。
分割代入は使用例を通じて説明するのが一番なので、ここではあなたが通読して学ぶためのいくつかの例を紹介します。
分割代入を使えば、例えば値を交換することができます:
var a = 1; var b = 3; [a, b] = [b, a];
このコードを実行後、 b は 1 に、a は 3 になります。分割代入がなければ、2 つの値の交換には一時変数 (あるいは一部の低級言語では XOR 交換のトリック) が必要になります。
同様に、3 つ以上の変数を順に交換することにも使えます。
var a = 'o';
var b = "<span style='color:green;'>o</span>";
var c = 'o';
var d = 'o';
var e = 'o';
var f = "<span style='color:blue;'>o</span>";
var g = 'o';
var h = 'o';
for (lp = 0; lp < 40; lp++) {
[a, b, c, d, e, f, g, h] = [b, c, d, e, f, g, h, a];
document.write( a + '' + b + '' + c + '' + d + '' + e + '' + f + '' + g + '' + h + '' + "<br />");
}
このコードを実行すると、変数が循環する様子をカラフルな視覚情報として見ることができます。
上で出てきたフィボナッチ数のジェネレータの例に戻ってみると、"i" と "j" の新しい値を単一のグループ代入文で計算することによって、一時変数 "t" を除去することができます。
function fib() {
var i = 0, j = 1;
while (true) {
yield i;
[i, j] = [j, i + j];
}
}
var g = fib();
for (let i = 0; i < 10; i++)
print(g.next());
分割代入のおかげで、関数は複数の値を返すことができます。関数から配列を返すこと自体はいつでもできたものの、分割代入はさらなる柔軟性を提供します。
function f() {
return [1, 2];
}
見ての通り、すべての返り値を角カッコで囲んだ、配列に似た構文を使ってその結果を返します。この方法で任意の数の返り値を返すことができます。この例では、f() はその出力として {{ mediawiki.external('1, 2') }} を返します。
var a, b;
[a, b] = f();
document.write ("A is " + a + " B is " + b + "<br>\n");
['a, b'] = f() というコマンドは、関数の返り値を角カッコ中の変数に順番に代入します。a は 1 にセットされ、b は 2 にセットされます。
また、返り値を配列として受け取ることもできます。
var a = f(); document.write ( "A is " + a );
この場合、a は値 1 と 2 を含む配列です。
オブジェクトからデータを取り出すために、分割代入を使うこともできます:
let obj = { width: 3, length: 1.5, color: "orange" };
for (let[name, value] in Iterator(obj)) {
document.write ( "Name: " + name + ", Value: " + value + "<br>\n" );
}
これは、オブジェクト obj の全てのキー/値の組についてループされ、それらの名前と値を表示します。この場合、出力は以下のようになります:
Name: width, Value: 3 Name: length, Value: 1.5 Name: color, Value: orange
obj を囲む Iterator() は、JavaScript 1.7 では必須ではありません。しかし、JavaScript 1.8 では必須になるでしょう。これは配列での分割代入を可能にするためです({{ Bug(366941) }} を参照)。
それぞれのオブジェクトから興味のあるフィールドだけを取り出しながら、オブジェクトの配列を横断してループすることもできます。
var people = [
{
name: "Mike Smith",
family: {
mother: "Jane Smith",
father: "Harry Smith",
sister: "Samantha Smith"
},
age: 35
}, {
name: "Tom Jones",
family: {
mother: "Norah Jones",
father: "Richard Jones",
brother: "Howard Jones"
},
age: 25
}
];
for each (let {name: n, family: { father: f } } in people) {
document.write ( "Name: " + n + ", Father: " + f + "<br>\n" );
}
これは、name フィールドを n に、family.father フィールドを f に抜き出し、それを出力しています。これは people 配列のそれぞれのオブジェクトに対し行われます。出力はこのようになります:
Name: Mike Smith, Father: Harry Smith Name: Tom Jones, Father: Richard Jones
あなたはまた、興味のない返り値を無視することもできます:
function f() {
return [1, 2, 3];
}
var [a, , b] = f();
document.write ( "A is " + a + " B is " + b + "<br>\n" );
このコードを実行後、a は 1 になり、b は 3になります。値 2 は無視されます。あなたはこの方法で任意の(あるいは全ての)返り値を無視することができます。例えば:
[,,,] = f();
正規表現の exec() メソッドがマッチを見つけると、正規表現の全体にマッチした部分文字列を 1 つ目の要素に格納し、続いて正規表現内で括弧に囲まれたグループにマッチした部分文字列を順に格納した配列を返します。分割代入を使うと、全体のマッチを使う必要が無ければそれを無視して、配列の一部分のみを取り出すことが簡単にできるようになります。
// http / https / ftp 形式の URL にマッチする単純な正規表現 var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url); if (!parsedURL) return null; var [, protocol, fullhost, fullpath] = parsedURL;