※スマホ対応はしてません。

for文を仕様からじっくり見てみる。あとwhileとか。(配列とかおれおれAdvent Calendar2018 – 13日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の13日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 13日目

繰り返しにもいろいろあるよ。

  • for, while
  • for-in → 別稿
  • for-of → 別稿
  • for-await-of → 別稿
  • forEach() → 別稿

for 文

古より伝わる技法。JavaScriptに限らずだいたいの言語で利用できます。

const arr = [11, 22, 33];

for (let i = 0; i < arr.length; i++) {
  const item = arr[i];
  console.log(item);
}

なお普通に先頭から末尾まで繰り返す場合は forEach() を使う方が現代では多いかと。

さて for 文、実行順序としては、

  1. 初期化 let i = 0
  2. 検証 i < arr.length
    • false なら終了
  3. 繰り返し処理 console.log(item)
  4. 繰り返し終端の処理 i++
  5. 「2. 検証」へ戻る

というやつですね。誰向けの説明だこれ。

まあせっかくなんでじっくり考えてみてほしいんですけど、セミコロンで区切って3種類の式を記述しまして、順に呼ばれるわけです。

条件を書く2番目は true/false の判定があるのでアレですが、他は何を書いても良いことになります。 例えば初期化で let を書かなくても良いし、終端式でカウンターを変化させる必要もありません。 というか2番目も省略可能で、その場合常に true 扱いになります。

いっそ省略して for (;;) でもよろしい。

あ、「終端式」とかは

おれが勝手に呼んでるだけです。

MDNでは以下のように紹介されている。

for ([initialization]; [condition]; [final-expression]) statement

ECMAScript的には基本的にどれもただの「式 expression」で、特に名前は決まっていないように思う。あえて探して言うなら……2番目の式が「試験 test」、3番目は「増進 increment」か。1番目は、うーん、そういう定義の仕方してないしなあ。

const は(だいたい)だめ

だめじゃないんだけど、終端式のところで i++ とあるように、変数に格納される値を変えながら繰り返すのが普通の for 文です。 値を変えるので、 const ではなく let である必要があります。

逆に終端式で変数値を変更しない場合、例えば副作用で内部情報が変わるだけとか、いっそ何もしないとか、そういう場合は const でも構いません。

for 文の仕様、第1式と本文

for 文なんて皆知ってておもしろくないと思うので、仕様の方もあたってみました。

第1式、 let i = 0 とか書く部分なんだけど、ここは仕様的には以下の3種類に分類されている。

  • ただの式(宣言なしで i = 0 とか)(省略も含む)
  • var 宣言
  • let, const 宣言

いずれかのパターンで初期処理を終えたのち、本文の評価へと移ります。

ただの式(宣言なしで i = 0 とか)(省略も含む)

普通です。

第1式が与えられた場合、式を評価して結果を取得します。 取得するけど、特に使いません。 まあgetterが実行されるぞと。

与えられない場合、何もしません。

var 宣言

普通です。

まず for 文に限らず var で宣言された変数は事前に準備されています。( for 文においては仕様書13.7.4.5 VarDeclaredNamesと13.7.4.6 VarScopedDeclarationsの部分。(だと思う。))

その後 for 文実行時に var xxx の xxx の部分を評価します。

let, const 宣言

ややこしくて長いです。スコープがいくつも出てきます。

簡単なコード例とスコープの範囲。外側から関数(赤)、forヘッダー(青)、for本文(紫)、for本文ブロック(緑)
「外側(赤)」「繰り返し全体(青)」「繰り返し毎回(紫)」「本文ブロック(緑)」の4つのレキシカル環境。

for 文実行時、まず現在の実行コンテキストの、えーとなにレキシカル環境? (LexicalEnvironment) を元に、繰り返し全体用のレキシカル環境を用意します。 そこに let なり const なりの変数を作成、宣言の内容を評価します。

本文ブロック {…} 実行時はまた新しいレキシカル環境が用意されるので、都合「外側(赤)」「繰り返し全体(青)」「本文ブロック(緑)」の3つのレキシカル環境が生まれることになります。へーそうなんだ。

加えて、 let の場合は繰り返し全体とブロックの間にもうひとつ、「繰り返し毎回(紫)」を作成します。次項。

あ、これらの命名はおれです。

また本文の評価終了後、途中でエラーが発生していたとしても、レキシカル環境を元に戻します。途中でコケると const が露出するとか嫌だもんね。

let 用のお役立ち追加レキシカル環境

第1式の評価が終わったので本文の評価へ移るところですが、その前にもう一点やることがあります。

ヘッダー (…) で let を利用している場合、繰り返しの度に新しいレキシカル環境を作り、それらの変数を移すという特殊な動きがあります。

さっきの例だと初回は「繰り返し全体(青)」のものを「繰り返し毎回(紫)」へ、以後「繰り返し毎回(紫)」から次の「繰り返し毎回(紫)」へ、都度複製してる感じ。

簡単なコード例とスコープの範囲。外側から関数(赤)、forヘッダー(青)、for本文(紫)、for本文ブロック(緑)。
for で宣言された let i (青)は本文ブロック(緑)からは参照されず、繰り返し毎回(紫)の部分へ複製されたものが見られる。毎回複製されるので、クロージャーで後から利用しても値は変化していない。

これは for で繰り返しのたびに生成されるけれど、ブロック {…} のものとは別に用意される。(ちなみにブロックのは繰り返しごとに毎回作られて消えるので、その中の変数も他に参照がなければ消えます。)

一体これの何が嬉しいかというと、あの!  for 文の罠が! 回避されます!!

// ç½ 
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var(1)', i), 1);
}
// => 3, 3, 3

// 罠回避
for (var j = 0; j < 3; j++) {
  (function (j) {
    setTimeout(function () {
      console.log('var(2)', j);
    }, 1);
  })(j);
}
// => 0, 1, 2

// やったぜ
for (let k = 0; k < 3; k++) {
  setTimeout(() => console.log('let(1)', k), 1);
}
// => 0, 1, 2

かつて var だった頃は、繰り返しで用いる i は外側のスコープに存在しているため、クロージャーで利用する頃には更新されて終了値になってしまっていました。(ひとつめ)

それを回避するため、匿名関数を作成して新しいスコープを作ったり、普通に関数の引数に与えたりして別のスコープへその値を与え、ある意味コピーするみたいなことが必要でした。(ふたつめ)

let だとその必要がないと。やったぜ。(みっつめ)

本文

for 文の宣言部分 (…) 終了後、第2式の評価が falsy になるか break 等が行われるまで、本文を繰り返します。

ブロック {…} を伴う本文実行時には毎回新しいレキシカル環境が作成されるので、繰り返し本文に const 宣言を記述してももちろん大丈夫。(なおブロックがなしに直接 const 書けません。)

外側で宣言された変数を、こう、上書きすることもできます。(表現微妙だけど察して。)

for (let i = 0; i < 3; i++) {
  const i = 0;
  console.log(i); // 全部0
}

まあ変数名が重複すると読みづらいのでやめた方が良いけどね。

レキシカル環境 (LexicalEnvironment) ?

簡単に言うといわゆるスコープのこと。もうちょっと言うとブロック構文その他に紐づいて変数等を格納するのもの。

と理解してるけど合ってるかな? よくわかってないです。というか他の呼び方もあるのかな。

while

ただ条件式を評価して、 true である場合は続く本文 { … } を実行します。

実務ではだいたい for 文の出番の方が多いと思うんだけど、こっちはこっちでよく使います。

なんかうまく言えないけど、使い勝手が良い場面がしばしばある。 分野によってはむしろこっちの方が利用回数多かったりしそう。

利用例

DOMで祖先要素を辿る例

単なる i++ じゃないやつ。

<div class="block1">
  <div class="block2">
    <div id="target"></div>
  </div>
</div>
const target = document.querySelector('#target');
for (let el = target; el; el = el.parentNode) {
  console.log(el);
}

// => <div id="target">
//    <div class="block2">
//    <div class="block1">
//    <body>
//    <html>
//    #document

反復

for-of でやれるやつだけど、分解してただの for 文でやることもできます。

function* generate () {
  yield 11;
  yield 22;
  yield 33;
}

const iterator = generate();
for (let step = iterator.next(); !step.done; step = iterator.next()) {
  const { value } = step;
  console.log(value);
}

ジェネレーターの説明以外に特に利点はなさそう。

無限ループ

何らかの理由により無限ループしたい場合にも使います。

for (;;) {
  // 実行され続けるコード
}
while (true) {
  // 実行され続けるコード
}

無限ループと見せかけて中で break してる場合も多い。 終了条件がある程度複雑で括弧 ( ... ) 内に書きたくない場合、わざとこういう書き方することもあります。

可能なら関数化したいところ。

条件を満たすまで待つ

ハードウェアに近い制御をするときとかにたぶんよく書くやつ。知らんけど。 break より return する場面の方が多いんじゃないかな、いや知らんけど。

while (true) {
  const result = doSomething();
  if (result === CODE_OK) {
    break;
  }

  sleep(100);
}

普通JavaScriptでこういうの書くことはないと思う。

なお sleep() は各自ご用意ください。

実行されないコード

どこぞの文化ではコメントアウト替わりに使うこともあるとか? 良くないと思うなー。

while (false) {
  // 実行されないコード
}

その他

break, continue

そこそこ実務でも使うことがあります。

あるでしょ? (ある。)

for より while で使うことの方が多い気がするな。いやそうとも限らないか。

const arr = [11, 22, 33];

for (let i = 0; i < arr.length; i++) {
  const item = arr[i];

  // 偶数は無視
  if (item % 2 === 0) {
    continue;
  }
  
  console.log(item);
}
const arr = [11, 22, 33];

for (let i = 0; i < arr.length; i++) {
  const item = arr[i];
  console.log(item);

  // 22が出てきたら満足して終了
  if (item === 22) {
    break;
  }
}

ラベル

実務で使ったことないです。

ないでしょ? (ない。)

入れ子になった繰り返しをまとめて break したりできます。

この例↓だと console.log() は一度しか実行されません。

const arr = [...Array(30)].map((_, i) => i);

outerLoop: for (let i = 0; i < arr.length; i++) {
  for (let j = 0; j < arr.length; j++) {
    console.log(i, j);
    break outerLoop;
  }
}

ベーシックのような古い言語で GOTO 命令と組み合わせて使ってた印象。 現代ではBad practiceの類です。 やめよう、というかまあ使おうとも思わないだろうけど。

do-while

実務で使ったことないです。

ないでしょ? (ない気がする。)

「空でもひとつオブジェクト作らなくちゃ」みたいなのは if で分ける方が良いと思います。

あ、TreeWalkerが do-while に良さそうな仕様だった。

<ul id="root">
  <li id="node-1"><span id="node-1-1">One</span></li>
  <li id="node-2"><span id="node-2-1">Two</span></li>
  <li id="node-3"><span id="node-3-1">three</span></li>
</ul>
const root = document.querySelector("#root");
const w = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
do {
  console.log(w.currentNode.id);
} while (w.nextNode())

// => root
//    node-1
//    node-1-1
//    node-2
//    node-2-1
//    node-3
//    node-3-1

do

繰り返しは関係ないんだけど、 do-while が出たので。

do 式という仕様案が出てます。(案なのでまだ使えない。)

代入 = の右辺に置いて、即時実行関数の戻り値を変数に入れるみたいな使い方ができるもの。Kotlinの run 的なやつ。

const timeText = do {
  const d = new Date();
  `${d.getHours()}:${d.getMinutes()}`
};

これ便利だと思うなー。ほしい。今でも匿名関数の即時実行で近い書き方ができるけど、こっちの方がいいなあ。

おしまい

こんなん読んで誰が嬉しいんだ……みたいに思いながら書きました。 おれは楽しかったよ!

参考

更新履歴

  • 2018-12-13 「 let 用のお役立ち追加レキシカル環境」を追加、それに合わせて前後調整

その名の通りのfilter()で絞り込み。(配列とかおれおれAdvent Calendar2018 – 12日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の12日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 12日目

配列の中から条件に合致するものだけに絞るやつです。

const users = [
  { id: 101, name: 'Alice', active: true },
  { id: 102, name: 'Bob', active: false },
  { id: 103, name: 'Charlie', active: true },
];

const activeUsers = users.filter((v) => v.active);
console.log(activeUsers);

仕様

配列のこれ系のメソッドと同じです。

newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

引数

filter() は引数に、関数オブジェクト callback(element[, index[, array]]) をひとつ受け取ります。

与える関数には、3つの引数が与えられます。 また前述のように boolean 値を返してください。

  • value … 配列の要素
  • index … インデックス
  • array … 操作中の配列本体

戻り値

関数 callback が true (ないし truthy なもの)を返した際の要素だけを格納した、新しい配列を返します。

該当がなかった場合でも要素数0の配列を返します。

例

undefined を除く

何かやった結果として undefined 混じりの情報を得るみたいな場面。

const result = [
  'foo',
  undefined,
  '',
  'hoge',
  undefined,
  'bar',
];

const validResult = result.filter((v) => v !== undefined);
console.log(validResult); // => [ 'foo', '', 'hoge', 'bar' ]

空文字列等の falsy なものもまとめて除いで良い場合は (v) => v で。短い。

アクティブユーザーだけ

フラグを含むオブジェクトの配列から対象のものだけを抜き出す場面。

const users = [
  { id: 101, name: 'Alice', active: true },
  { id: 102, name: 'Bob', active: false },
  { id: 103, name: 'Charlie', active: true },
];

const activeUsers = users.filter((v) => v.active);
console.log(activeUsers);
// => [ { id: 101, name: 'Alice', active: true },
//      { id: 103, name: 'Charlie', active: true } ]

チェックボックスで選択されたものだけ

document.querySelectorAll() は複数の要素を配列風オブジェクト NodeList で返します。 [...els] でそれを本物の配列へ変換し、 filter() で選択されたものだけにしてから map() で要素からその値を取り出しします。

(コードは抜粋)

<form id="the-form">
  <input type="checkbox" name="favorite" value="apple"> Apple
  <input type="checkbox" name="favorite" value="banana"> Banana
  <input type="checkbox" name="favorite" value="orange"> Orange
</form>
const elFavorites = document.querySelectorAll('#the-form [name="favorite"]');
const favorites = [...elFavorites]
  .filter((el) => el.checked)
  .map((el) => el.value);
console.log(favorites);

他の配列で指定されたID

前項のチェックボックスがIDだったとして、また別の配列からをその選択されたやつだけに絞る場合。

const users = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];
const selectedIds = ['101', '102'];

const activeUsers = users.filter((v) => selectedIds.includes(v.id));
console.log(activeUsers);
// => [ { id: '101', name: 'Alice' }, { id: '102', name: 'Bob' } ]

その他のメソッド

「絞り込む」以外の用途に合うメソッドがあるので、それぞれ使い分けましょう。

絞り込んだ結果を使って何かするなら map()

find() 後に map() を組み合わせると便利です。

前述したチェックボックスの例を参考に。

ひとつだけ選択する

対象のものが1つとわかっている場合は find() が便利です。

const users = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];

const id = '102';
const targetUser = users.find((v) => v.id === id);
console.log(targetUser);
// => { id: '102', name: 'Bob' }

あるかどうかだけわかれば良いなら some()

const existing = users.some((v) => v.id === targetId);
if (existing) {
  console.log('あったよ。');
}
else {
  console.log('なかったよ。');
}

全てが合致するか確認するなら every()

おまけ: あのアイコン

配列とは関係ないんだけど、UI上でフィルターの意味でしばしば使われるじょうご(ろうと、漏斗)、英語だと funnel だそうです。

Font Awesomeの “filter” 。

ガンダムのファンネルは、最初はその形状から名づけられたとか。その後ファンネルだけで例の小型遠隔兵器を指すようになり、フィン・ファンネルとかシールド・ファンネルとか出てくると。発展するにつれ語源から遠ざかるのは世の常ですね。

今日こんにちは! 然様さようなら!

おしまい

関連

参考

querySelectorAll()の結果はNodeListだけどforEach()が使える仕様です。(配列とかおれおれAdvent Calendar2018 – 11日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の11日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 11日目

querySelectorAll() で取ってきた結果は NodeList という配列風オブジェクトだけど、配列ではありません。

つまり、配列が持つ各種のメソッドは使えません。 使いたい場合は配列へ変換してやる必要があります。

しか forEach() については近年使えるようになりました。

const els = document.querySelectorAll('.target');
els.forEach((el) => {
  console.log(el.textContent);
});

中身は配列の forEach() と完全に同じです。

配列ではないことの確認

const els = document.querySelectorAll('.target');
console.log(els.length, els[0]);
console.log(Array.isArray(els)); // => false
console.log(els.constructor.name); // => "NodeList"

仕様

現在のLiving Standardの記述は、こう。

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

この iterable はWebIDLのやつです。よね? そっちで forEach() が定義されてます。

If the interface defines an indexed property getter, then the Function object is the initial value of the “forEach” data property of %ArrayPrototype% ( [ ECMA-262] , section 6.1.7.4).

インターフェイスがindexed property getterを定義している場合、その関数オブジェクトは%ArrayPrototype% ( [ ECMA-262] , section 6.1.7.4) の “forEach” の初期値である。

実際そうなってる。

console.log(NodeList.prototype.forEach === Array.prototype.forEach); // => true

利用可能な環境

全てのウェブブラウザーで利用可能です。(IEはもはやブラウザーではない。)

IE以外は全て対応済み。

対応していない場合

いろいろPolyfillあるはずなんで探してください。

その他のメソッド

以下も利用可能です。

  • entries()
  • keys()
  • values()

配列のメソッドを利用するなら call()

8日目に書いたんだけど、配列のメソッドは配列風オブジェクトで動くように設計されています。

というわけで、 call() しよう。

const els = document.querySelectorAll('.target');
const texts = Array.prototype.map.call(els, (el) => el.textContent);
console.log(texts);

配列へ変換するなら [...els]

場合によってはいっそ変換してしまう方が楽かも。

色々やり方はあるけれど、スプレッド構文 ... が一番楽かなと思います。

const els = document.querySelectorAll('.target');

const arr = [...els];

console.log(arr.length, arr[0]);
console.log(arr instanceof Array); // => true
console.log(arr.constructor.name); // => "Array"

例

短いので [...els] の方で書きました。

フォームのテキスト入力値を全て取得する例

そういう用途だと map() が便利です。

<input type="text" class="text-input" value="Apple">
<input type="text" class="text-input" value="Banana">
<input type="text" class="text-input" value="Orange">
const elInputList = document.querySelectorAll('.text-input');
const values = [...elInputList].map((el) => el.value);
console.log(values); // => [ "Apple", "Banana", "Orange" ]

これはテキスト入力だけだけど、中で el.type を見て分岐したりすればもっと細かく各種取得できますね。チェックボックスを配列にしたりってのまで考えると reduce() の方が良いかもな。

近年使えるようになりました?

いつからだっけ?

まだ使えなかった頃の質問。

MDNの互換性の表によるとChrome 51からだそうで、そうなると2016年5月のようです。

結構前だなー。

おしまい

forEach() があるだけで相当便利です。

map() も使いたいけど、 NodeList を map() した結果が NodeList にならないのも気味が悪い気がするし、まあ仕方ないかなあ。

関連

参考

任意の要素数の配列を作る方法7通り。(配列とかおれおれAdvent Calendar2018 – 10日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の10日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 10日目

最初に結論なんだけど、たぶん [...Array(length)].map(fn) が良いかなと。

const arr = [...Array(3)].map((_, i) => i);
console.log(arr); // => [ 0, 1, 2 ]
console.log(arr.length); // => 3

... で展開

配列オブジェクトは反復可能 (iterable) なので、スプレッド構文 ... で展開してやることができます。

const arr = [...Array(3)];
console.log(arr); // => [ undefined, undefined, undefined ]
console.log(arr.length); // => 3

短いし良さそう。

ただ記号の組み合わせになるので、初見でぎょっとするかも? すぐ慣れるとは思うけれど。

map() と組み合わせて初期値

初期値も設定できます。初期値じゃないけど実質初期値。

const arr = [...Array(3)].map(() => 0);
console.log(arr); // => [ 0, 0, 0 ]

オブジェクトを初期値にする場合は注意

オブジェクトを作る場合、丸括弧 () で括るのを忘れないように。 => に続けて波括弧 {} を書くと違う意味になるので。

const arr = [...Array(3)].map((_, i) => { id: i + 1 });
console.log(arr); // => [ undefined, undefined, undefined ]
const arr = [...Array(3)].map((_, i) => ({ id: i + 1 }));
console.log(arr); // => [ { id: 1 }, { id: 2 }, { id: 3 } ]

他のやり方も同じね。

new Array(length)

これが一番素直なやりかたではあるが、項目が空になってしまうという問題がある。

const arr = Array(3);
console.log(arr); // => [ <3 empty items> ]

arr.forEach((value) => {
  console.log(value); // ←一度も呼ばれない
});

ここでいう空は undefined が入っているわけではなく本当に空なので、 forEach() や map() が利きません。

というわけでだいたいの場合、こいつを起点にしてもうちょっとずつコードをこねこねしてやる必要があります。

new

はあってもなくても同じです。

const arr = new Array(3);

詳しくはこちら。

Array.from()

初見のわかりやすさならこちらか。

const arr = Array.from({ length: 3 });
console.log(arr); // => [ undefined, undefined, undefined ]
console.log(arr.length); // => 3

可読性は悪くないが、ちょっと記述量が多いような?

初期値で埋める

第2引数へマップ関数与えられるので、 undefined 以外を設定したい場合に大変便利。

const arr = Array.from({ length: 3 }, () => 0);
console.log(arr); // => [ 0, 0, 0 ]

fill()

map() 等は空の項目を飛ばすんだけど、 fill() は飛ばさずに埋めてくれます。

const arr = Array(3).fill(0);
console.log(arr); // => [ 0, 0, 0 ]
console.log(arr.length); // => 3

undefined 以外にも任意の値で生成できるし、ハック的な不明瞭さもないし、良いんじゃないすかね。

ただし、オブジェクトを入れるのはだめです。

オブジェクトで埋めちゃだめ

各要素が同じオブジェクトのインスタンスを指してしまいます。

const arr = Array(3).fill({ a: 1 });

arr[0].a = 2;
console.log(arr[0]); // => { a: 2 }
console.log(arr[1]); // => { a: 2 }
console.log(arr[0] === arr[1]); // => true

というわけで、やるならこう。

const arr = Array(3).fill().map(() => ({ a: 1 }));

結局長くなってしまった。 しかもメソッドを連鎖して呼び出すので、一行で書くには見た目がちょっと。

join() からの split()

一度文字列に変換してから、配列へ再変換します。

const arr = Array(3).join(',').split(',');
console.log(arr); // => [ '', '', '' ]
console.log(arr.length); // => 3

要素は空文字列 '' になります。

二段階あるのがハックっぽくて可読性低め。

apply()

関数オブジェクトのメソッド apply() です。

const arr = Array.apply(null, Array(3));
console.log(arr.length); // => 3
console.log(arr); // => [ undefined, undefined, undefined ]

利点は古い環境でも使えるところ。

for で頑張る

いやあこれはちょっと。

const arr = Array(3);
for (let i = 0; i < arr.length; i++) {
  arr[i] = undefined;
}

console.log(arr); // => [ undefined, undefined, undefined ]
console.log(arr.length); // => 3

おしまい

というわけで、 [...Array(3)].map(fn) がよろしいかと存じます。

関連

参考

配列初期化子 [] のひみつ。(配列とかおれおれAdvent Calendar2018 – 09日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の9日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 09日目

[] のやつです。初期化子 (initializer) と呼ばれます。 「リテラル」と呼んでも差し支えない。はず。

説明

仕様書では以下のように説明されています。

12.2.5 Array Initializer

(Note: An ArrayLiteral is an expression describing the initialization of an Array object, using a list, of zero or more expressions each of which represents an array element, enclosed in square brackets. The elements need not be literals; they are evaluated each time the array initializer is evaluated.)

Array elements may be elided at the beginning, middle or end of the element list. Whenever a comma in the element list is not preceded by an AssignmentExpression (i.e., a comma at the beginning or after another comma), the missing array element contributes to the length of the Array and increases the index of subsequent elements. Elided array elements are not defined. If an element is elided at the end of an array, that element does not contribute to the length of the Array.

12.2.5 配列初期化子

(ノート:ArrayLiteral は配列オブジェクト生成を記述する式。角括弧に囲まれた、それぞれが配列要素を表す0個以上の式から成るリストを用いたもの。要素はリテラルである必要はなく、配列初期化子が評価されるたびにそれらも評価される。)

要素リストの先頭や途中、末尾の配列要素は省略してよい。 要素リスト中のカンマが AssignmentExpression に続いていない (not preceded) 場合(例:先頭や他のカンマの後ろのカンマ)、欠落した配列要素は配列の長さに影響し、続く要素のインデックスを増やす。 省略された配列要素は定義されない (not defined) 。 配列の最後の要素が省略された場合、その要素は配列の長さに影響しない。

まとめ

  • [ item1, item2, … ] という形
    • このうち item1 等が AssignmentExpression
  • 要素を省略してカンマだけ書ける(例えば [ ,,, ] も正しい)
  • 末尾の省略は無視されるので、 [ item1 , ] と [ item1 ] は同じ

他の配列の要素を取り込む

スプレッド構文 ... を利用して、他の配列を新しい配列の一部として利用できます。

const arr1 = [11, 22, 33];
const arr2 = [0, ...arr1, 99];
console.log(arr2); // => [ 0, 11, 22, 33, 99 ]
console.log(arr2.length); // => 5

... を付けないと、展開されないので、なんかこう、残念な結果になります。

const arr1 = [11, 22, 33];
const arr2 = [0, arr1, 99];
console.log(arr2); // => [ 0, [ 11, 22, 33 ], 99 ]
console.log(arr2.length); // => 3
console.log(arr2[1]); // => [ 11, 22, 33 ]

要素の省略

要素の記述なしにカンマ , だけ置くことで、省略 (elision) が発生します。

const arr = [11, , 22];
// --------------^
console.log(arr); // => [ 11, <1 empty item>, 22 ]
console.log(arr.length); // => 3

arr[1] は undefined を返しますが、それが値として設定されているわけではありません。

empty itemsのところで説明します。

末尾カンマ

いわゆるケツカンマ。

最初にいきなりカンマ , を置くとempty slotになるけれど、末尾にカンマを置いた場合は何も起こりません。

const arr = [
  11,
  22,
  33,
];
console.log(arr); // => [ 11, 22, 33 ]

一行で書いても同じ。

const arr = [11, 22, 33, ];
console.log(arr); // => [ 11, 22, 33 ]

[] は確実に配列を生成する

配列初期化子 [] を用いた場合、内部に持っている固有オブジェクト %ArrayPrototype% をプロトタイプとしてオブジェクトが生成されます。そのため、 window.Array や global.Array やらを置き換えたとしても影響はありません。

昔のJavaScriptでは置き換えると影響が出ていたんだとか。

In JavaScript 1.5 and earlier versions, it was possible to override Primitive Object’s constructor, and have this overridden version called when using bracket notations.

JavaScript 1.5以下のバージョンでは、プリミティブオブジェクトのコンストラクターをオーバーライドすることが可能でした。そして角括弧表記を使うとそのオーバーライドされたものが呼ばれててしまうのでした。

1.5は相当前だなあ。

角括弧 [ ] の名前

日本語では角つの括弧、英語ではブラケット brackets です。これ豆な。

おしまい

よく使うよね、配列リテラル。コンストラクターから作るひとはなかなかいないと思う。

気付いたんだけど、この文法は読みやすいので、仕様書を初めて読んでみる方にもおすすめです。

入れ子になって定義されている。他の章へ飛ぶ部分が比較的少ない。

関連

参考