※スマホ察応はしおたせん。

だいたいの繰り返しは配列のforEach()でいける。配列ずかおれおれAdvent Calendar2018 – 17日目

カテゎリヌ: JavaScript

LINDORのAdvent Calendar本物の16日目を開けたずころ。
配列ずかおれおれAdvent Calendar2018 – 16日目

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

お埅たせ 珟代的JavaScriptでは䞻流の、よく for を眮き換え䜿うや぀です。

たず for の䟋

const arr = [11, 22, 33];

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

forEach() にした䟋

const arr = [11, 22, 33];

arr.forEach((value) => {
  console.log(value);
});

かんたヌん。

簡単なので、あたり語るこずはありたせん。

仕様

匕数は他のこれ系の配列メ゜ッドず同じです。

forEach() は匕数に、関数オブゞェクトをひず぀受け取りたす。 戻り倀はありたせん。 (undefined)

䞎える関数は、3぀の匕数が䞎えられたす。

  • value 
 配列の芁玠
  • index 
 むンデックス
  • array 
 操䜜䞭の配列本䜓
const arr = [11, 22, 33];

arr.forEach((value, index, array) => {
  console.log(index, ':', value, ' <- ', array[index], array);
});

第2匕数

実は forEach() その他にはには第2匕数 thisArg がありたす。

これは第1匕数の関数実行時に this ぞ束瞛されるオブゞェクトなんだけど、 近幎は this が倉わらないアロヌ関数を甚いるのが䞻流なので、 䜿う堎面は少ないかなず思いたす。

const obj = {
  arr: [11, 22, 33],

  logAll: function () {
    this.arr.forEach(function (value) {
      this.output(value);
    }, this);
  },

  output: function (msg) {
    console.log('Message: ', msg);
  },
};

obj.logAll();

再利甚

これも他の配列メ゜ッドず同様なんだけど、配列以倖のオブゞェクトぞ適甚しおも適切に動䜜するよう蚭蚈されおいたす。

䟋

基本的に forEach() でできお for でできないこずっおないんじゃないかな。

普通に繰り返す

const arr = [11, 22, 33];
arr.forEach((value) => {
  console.log(value);
});

配列内の他の芁玠を参照する

えヌず䟋えば毎幎の人口ずか、そういう数倀情報が䞎えられお、その増枛を芋おいきたい堎合。

たずは for 文でやる䟋。

const arr = [100, 110, 115, 103, 110, 90];

for (let i = 1; i < arr.length; i++) {
  const item1 = arr[i - 1];
  const item2 = arr[i];
  const diff = item2 - item1;
  const sign = diff < 0 ? '' : '+';
  console.log(`${item1} -> ${item2} (${sign}${diff})`);
}

// 100 -> 110 (+10)
// 110 -> 115 (+5)
// 115 -> 103 (-12)
// 103 -> 110 (+7)
// 110 -> 90 (-20)

たず最初に i = 1 から始めるのはできないので、繰り返しの䞭で飛ばしたす。slice() ずかするずむンデックスがずれちゃうので泚意。そっちの方が良いかもだけど。

たた配列党䜓は䞎える関数の第3匕数にもらえるので、これを利甚したす。

const arr = [100, 110, 115, 103, 110, 90];

arr.forEach((item2, i, all) => {
  if (i < 1) { return; }
  const item1 = all[i - 1];
  const diff = item2 - item1;
  const sign = diff < 0 ? '' : '+';
  console.log(`${item1} -> ${item2} (${sign}${diff})`);
});

ここで all は倖偎の arr ず同じなので、そっちでも良いです。

結果を配列にしたいなら reduce() も有甚。

同じ凊理を繰り返す

配列蚘法から盎接メ゜ッド実行できるので、匿名関数の即時実行みたいなノリで、匿名配列の即時利甚おな感じでも䜿えたす。

[
  '.target1',
  '.target2',
  '.target3',
].forEach((selector) => {
  const el = document.querySelector(selector);
  el.classList.add('targeted');
});

同じ凊理をたずめるっおなら関数化が正解だず思うんだけど、手軜に曞きたいずきずか。

セミコロンを行末に眮かないスタむルの堎合はご泚意ください。配列蚘法 [] が前の行ず繋がっおしたうので。

䌌た蚈算をする

瞊暪䞡方向の蚈算ずか、プロパティ名は異なるが算出方法は同じ、みたいな堎面で。

// 抜粋

[
  ['clientWidth', 'left'],
  ['clientHeight', 'top'],
].forEach(([sizeName, axisName]) => {
  const pos = (elWrapper[sizeName] - elItem[sizeName]) / 2;
  elItem.style[axisName] = `${pos}px`;
});

玔粋な蚈算ず副䜜甚を分けたい堎合は map() でも。

forEach() でできないこず

ず、他ず組み合わせおのやり方。

forEach() は「先頭から末尟たで繰り返す」ものなので、それ以倖のパタヌンで繰り返したい堎合はコヌドこねこねしおやる必芁がありたす。

先頭以倖から、末尟以倖たでの繰り返し

あんたりやらない気もするけど、途䞭からずか途䞭たでずかはできたせん。

split() ず組み合わせたす。

const arr = ['A', 'B', 'C'];

// 先頭から1個たでを飛ばす
// => B, C
arr.slice(1).forEach((item, i) => {
  console.log(`${i}: ${item}`);
});

// 末尟から1個たでを飛ばす
// => A, B
arr.slice(0, -1).forEach((item, i) => {
  console.log(`${i}: ${item}`);
});

index を芋おコヌルバック関数で return するのでもアリ。

飛ばしお回す

䞀぀飛ばしずかはできたせん。

できないので、繰り返しのコヌルバック関数で郜床 return しお、䜕もしないようにしたす。

const arr = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'];

// 3぀ごず
// => A, D, G, J, ...
arr.forEach((c, i) => {
  if (i % 3 !== 0) { return; }
  console.log(`${i}: ${c}`);
});

あるいは事前に匟いおおく。こっちの方が芋た目はきれいっぜい。

const arr = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'];

// 3぀ごず
// => A, D, G, J, ...
arr
  .filter((_, i) => i % 3 === 0)
  .forEach((c, i) => console.log(`${i}: ${c}`));

逆順に繰り返し

意倖ずこれができたせん。垞に先頭からです。

配列を逆順にする reverse() ずいうのもあるんだけど、こい぀は察象の配列自䜓を逆順にする砎壊的な副䜜甚があるので、利甚可胜な堎面が限定的です。

const arr = ['A', 'B', 'C'];

arr.reverse().forEach((item, i) => {
  console.log(`${i}: ${item}`);
});
// => C, B, A

// 元の配列が倉わっちゃう
console.log(arr); // => [ 'C', 'B', 'A' ]

事前に耇補しおから reverse() すればいいんだけど。

[...arr].reverse()

うヌん、 reduceRight() が良いかな。

const arr = ['A', 'B', 'C'];

arr.reduceRight((_, item, i) => {
  console.log(`${i}: ${item}`);
}, 0);

これはこれで第2匕数に䜕か䞎えるのず、コヌルバック関数の第1匕数も value でないものが䞎えられるのを忘れないように気を付けないずいけない。

回っおくる倀を無芖する

const arr = ['A', 'B', 'C'];

arr.forEach((_, i) => {
  const item = arr[arr.length - 1 - i];
  console.log(`${i}: ${item}`);
});

sort() しちゃうのが意味が明瞭で䞀番良いかもしれない。

配列以倖の forEach()

普通のオブゞェクト

配列ぞ倉換しおやりたす。

プロパティ名だけ埗る䟋。

const obj = {
  a: 11,
  b: 22,
  c: 33,
};

Object.keys(obj).forEach((key) => {
  console.log(key);
});
// => a, b, c

他に倀だけを埗る Object.values() ず、䞡方を埗る Object.entries() がありたす。

for-of でやったや぀ら。

Map, Set

こい぀らは forEach() を持っおたす。だいたい同じ動き。

Map はむンデックスの代わりにキヌが埗られたす。 ですよねヌ。

順序ははキヌを远加した順。

const map = new Map([
  ['foo', 11],
  ['bar', 22],
  ['boo', 33],
]);
map.forEach((value, key) => {
  console.log(key, value);
});

Set の堎合、むンデックスやキヌずなる郚分にも倀が䞎えられたす。 わお。

こちらも順序は远加順です。

const set = new Set([11, 22, 33]);
set.forEach((value, v2) => {
  console.log(value, v2, value === v2); // 11, 11, true 等
});

DOM系の配列颚オブゞェクト

でも䜿えたりしたす。

const els = document.querySelectorAll('.target');
console.log(els instanceof Array); // => false
els.forEach((el, index) => {
  console.log(index, el);
});

forEach() 以倖の、䟋えば map() ずかはないです。

䞭身は完党に配列のそれず同じ。そこら蟺のもうちょい詳しい話を別皿に甚意したした。

jQuery

には each() ずいうのがあったり。

const $els = $('.target');
$els.each((index, el) => {
  console.log(index, el);
});

匕数の順序が違う点に泚意。 今時䜿うのかはわからないけど。

jQueryオブゞェクトは反埩可胜なので、 [...$els].forEach(fn) も䜿えたす。

for ず速床面の比范

たず結論ですが、高速化のために forEach() を避けお for を採甚する必芁はありたせん。

可読性や蚘述の甚意さから考えおも forEach() の方が良いように思いたす。個人の感想です。

遅いかず蚀われれば、もちろん遅いんだけど、そこの遅さが問題になる状況は普通、既に砎綻しおたすから。それよりも前に気にするべき個所があるはずです。蚈算量 O(n2) ずかそういうや぀を考えお、ファむルアクセスやら画面再描画やらの遅いAPIに気を付けお。

2012幎の実隓がありたす。0.001ミリ秒以䞋の差です。0.001秒じゃないよ。

よっぜど極たった堎面では別だけど、たあ埮劙な速床性胜差ではなく機胜差で遞がうね。

あ、でも

䜕か探すずかで最埌たで繰り返す必芁がない堎合は forEach() じゃなくお find() ずか some() ずか、そういう適切なものを䜿おう。意味が明瞭になっお可読性も向䞊するし。

おしたい

繰り返しシリヌズおしたい。はヌ長かった。

forEach() は䟿利で倧倉よろしい。

関連

参考

非同期に繰り返すならfor-await-of構文が䜿える、けど䜿わない方が良いかも。配列ずかおれおれAdvent Calendar2018 – 16日目

カテゎリヌ: JavaScript

LINDORのAdvent Calendar本物の16日目を開けたずころ。
配列ずかおれおれAdvent Calendar2018 – 16日目

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

for-of の亜皮で、非同期に繰り返すや぀に察応したす。

const sleep = (ms) => new Promise((f) => setTimeout(f, ms));

async function* foo () {
  yield 11;
  await sleep(1000);
  yield 22;
  await sleep(1000);
  yield 33;
}

(async () => {
  for await (const value of foo()) {
    console.log(value);
  }
})();

// 11
// 22
// 33

仕様

for-in, for-of ず同じ章で説明されたす。

基本的な凊理もそれらず䞀緒。なので過去蚘事にも目を通しおおいお頂きたいです。なにずぞ。

async 内でのみ利甚可胜

await なので。

// OK
(async () => {
  for await (const v of obj) {
    // 

  }
});

// SyntaxError: Unexpected reserved word
(() => {
  for await (const v of obj) {
    // 

  }
});

非同期に反埩可胜なオブゞェクト

for-of では「反埩可胜」なオブゞェクトが利甚可胜で、それは適切な [Symbol.iterator]() メ゜ッドが蚭定されおいるもの、でした。

for-await-of で利甚可胜なオブゞェクトは「非同期反埩可胜」なもので、それは適切な [Symbol.asyncIterator]() メ゜ッドが蚭定されおいるもの、です。

for-of のずきず同じく、そういうオブゞェクトを自䜜するこずができたす。

const sleep = (ms) => new Promise((f) => setTimeout(f, ms));

const obj = {
  async* [Symbol.asyncIterator] () {
    yield 11;
    await sleep(1000);
    yield 22;
    await sleep(1000);
    yield 33;
  },
};

(async () => {
  for await (const value of obj) {
    console.log(value);
  }
})();

1000ミリ秒止たりながら繰り返す様子。

非同期のおさらい

軜く await のお話をしたす。知っおる人は飛ばしお次ぞ。

async ずは

関数を Promise 化するや぀です。

// Promise版
const f = () => {
  const promise = new Promise((resolve, reject) => {
    resolve(123);
  });
  return promise;
};
// async版
const f = async () => 123;

const p = f();
console.log(p instanceof Promise); // => true
p.then((result) => {
  console.log(result); // => 123
});

return するず resolve() 、 throw は reject() です。

今回の䟋だず非同期関数の䞭で䜕もしおないけど、もちろん普通は Promise なりたた await なりで非同期に凊理をしたす。

await ずは

Promise の then() の代わりです。

// promise-then版
p.then((result) => {
  console.log(result);
});
// async-await版
const result = await p;
console.log(result);

むンデントが深くならないずころが玠敵。

async な関数内でのみ利甚可胜です。 Chrome DevToolsのコン゜ヌルだず await 動くけど、あれは特別。 倖だず゚ラヌに。

SyntaxError: await is only valid in async function

catch() の代わりは構文の方の try-catch です。

fetch() の䟋

䟋えば、指定のパスのHTMLを取埗し解析、 <title> に蚭定されおいる文字列を埗るや぀。

const fetchTitle = (path) => fetch(path)
  .then((res) => res.text()) // text()はPromiseを返す
  .then((html) => html.match(/<title>(.*)<\/title>/i)[1]);

これ↑を、こう↓曞けたす。

const fetchTitle = async (path) => {
  const res = await fetch(path);
  const html = await res.text();
  return html.match(/<title>(.*)<\/title>/i)[1];
};

でもっお async が付いおる fetchTitle() は Promise オブゞェクトを返すので、こう䜿いたす。もちろんこい぀らも await でも良い。

// 珟圚ペヌゞのタむトルを取埗
fetchTitle(location.href)
  .then((title) => console.log('Title:', title));

// トップペヌゞのタむトルを取埗
fetchTitle('/')
  .then((title) => console.log('Title:', title));

䜿い方

話を戻しお for-await-of は、非同期反埩子 (AsyncIterator) が返す結果を await しながら反埩したす。

こんな非同期反埩子を返す関数があったずしたす。

async function* foo () {
  yield 11;
  await sleep(1000);
  yield 22;
  await sleep(1000);
  yield 33;
}

繰り返さない䟋

たずはここから。

// 普通の反埩子
const it = foo();
const result = it.next();
console.log(result);
// 非同期反埩子
const ait = foo();
const result = await ait.next();
console.log(result);

普通の for で繰り返す䟋

next() 呌び出すずころで await しおたすね。 for 文で同じようにしお、倀を非同期に埗ながら反埩するこずができたす。

const ait = foo();
for (let cur = await ait.next(); !cur.done; cur = await ait.next()) {
  const { value } = cur;
  console.log('for', value);
}

for-await-of で曞く䟋

長い for 文になっおしたったけれど、倧䞈倫、僕らには for-await-of 構文がありたす。

for await (const value of foo()) {
  console.log(value);
}

はいできあがり。

぀いでに、普通の for 文で普通に await する䟋

ここたでやっおきた for-await-of の特城は反埩子が非同期、ずいうずころなので、䜕でもないずころで非同期にやるなら普通に await するだけです。

for (const url of urls) {
  const res = await fetch(url);
  console.log(res);
}

可胜なら Promise.all() を

ずいうわけで色々曞いおきたんだけど、非同期に繰り返すのは良くないです。

だっおせっかく非同期に凊理できるのだから、順番ではなく䞀床にたずめお実行しお、その埌に結果を順に凊理しおいく方が高効率です。

䟋えば非同期凊理がひず぀500msかかるずしお、3぀順にやれば合蚈1500msの埅機時間が必芁になりたす。これらを Promise.all() で䞊列に実行しおやれば、埅ち時間は結局500msのたたです。

盎列実行は右ぞ、䞊列実行は䞋ぞ延びる。埌者の方が暪軞時間は短い。

繰り返し䞭の await を犁じるESLintの蚭定

ESLintにもそういう蚭定がありたす。

Performing an operation on each element of an iterable is a common task. However, performing an await as part of each operation is an indication that the program is not taking full advantage of the parallelization benefits of async/await.

Usually, the code should be refactored to create all the promises at once, then get access to the results using Promise.all(). Otherwise, each successive operation will not start until the previous one has completed.

反埩の各芁玠に察しお操䜜を行うこずは䞀般的な䜜業です。しかしながら、各段階の操䜜で await を実行するず、そのプログラムが async/await による䞊列化の恩恵を十分に享受できないこずになりたす。

䞀般にこのようなコヌドは、䞀床に党おのプロミスを䜜成しそのうえで Promise.all() を甚いお結果を埗るようリファクタヌされるべきです。そうでないず連続的な凊理 (successive operation) が前の凊理が完了するたで始たりたせん。

for-await-of を犁じるESLintの蚭定

ただし前項のものは普通の for 内で await 曞いたずきに匕っかける甚で、 for-await-of は匕っかからないです。

for-await-of を for-of は蚱容し぀぀匟く蚭定はこんな感じ↓です。

module.exports = {
  "rules": {
    'no-restricted-syntax': [
      'error',
      'ForOfStatement[await=true]',
    ],
  },
};

for-await-of ぱラヌ、 for-of はOKになる。

普通の for 文で普通に await しない䟋

䞊の方で曞いた繰り返しの途䞭で await する方の䟋を、 Promise.all() を䜿っお䞊列に実行しお、その党䜓を await で埅぀ようにした䟋です。

const responses = await Promise.all(
  urls.map((url) => {
    console.log(url);
    return fetch(url);
  })
);

for (const res of responses) {
  console.log(res);
}

耇数の fetch() が同時に走るので、こっちの方が速いです。

䟋

えヌず、非同期に繰り返す䟋かあ  。

同意を促すメッセヌゞ

ごめん思い぀かなかった。

// 抜粋

async function* waitForAgreeing (interval = 2000) {
  let index = 0;
  while (true) {
    if (elAgree.checked) {
      return;
    }

    await sleep(interval);
    yield messages[index];
    index = (index + 1) % messages.length;
  }
}

const main = async () => {
  for await (const message of waitForAgreeing()) {
    elMessage.textContent = message;
  }
  elMessage.textContent = 'さんきゅヌ';
};

2秒ごずに同意を促す文蚀が衚瀺され続ける䟋。
同意するたでメッセヌゞが曎新され続ける。

その他

for-of 自䜓があたり良くない

for-await-of を匟く蚭定を䞊の方で曞いたけど、そもそも for-of を匟くべき

AirbnbのESLintの蚭定をよく䜿っおるんだけど、そこでは for-of の利甚自䜓が犁止されおいたす。別途 for-in も犁止。

理由は蚭定に曞いおある。

iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.

むテレヌタヌ、ゞェネレヌタヌは再生成ランタむム (regenerator-runtime) が必芁で、それは本ガむドの蚱容範囲からするずあたりに重すぎたす。たたそれずは別に、ルヌプの利甚は回避し、配列の反埩を利甚しおください。

arr.forEach() を䜿えっおさ。 Object.keys() ずかもだめなのかな。どれくらい遅いんだろ。

ちなみに for ず比べお forEach() が遅すぎるっおこずはないです。

おたけ: for-await-in

for await (const key in obj) {
  console.log(key);
}

SyntaxError: Unexpected token in

そんなものはない。

おしたい

あんたり䜿いどころがなあ。 for でやれるこずだしせっかくだから for-of でもできるようにしおおこう、ずかそういう感じなのかなあ。

ああでも配列以倖のゞェネレヌタヌ自䜓の良い䜿い道がただわかっおないから、そこら蟺わかったら非同期に繰り返したくなるかも。

関連

参考

for-ofで配列も普通のオブゞェクトも反埩しよう。配列ずかおれおれAdvent Calendar2018 – 15日目

カテゎリヌ: JavaScript

LINDORのAdvent Calendar本物の15日目を開けたずころ。
配列ずかおれおれAdvent Calendar2018 – 15日目

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

for-in じゃない方です。 こっち䜿おう。

for-of 文

for-in ず違っお倀の方を持っおきおくれたす。

const arr = [11, 22, 33];

for (const value of arr) {
  console.log(value);
}

// 11
// 22
// 33

for に眮き換え

反埩可胜なオブゞェクトを察象ずする for-of を普通の for 文に眮き換えるこずもできたす。

const arr = [11, 22, 33];

const it = arr.values();
for (let cur = it.next(); !cur.done; cur = it.next()) {
  const { value } = cur;
  console.log('for', value);
}

仕様

for-in, for-of, for-await-of は党郚たずめお定矩されおる。

ので、 for-in の方もみずいおください。

for-in は列挙可胜なプロパティ名を埗たしたが、 for-of の方では反埩子 (iterator) を甚いた繰り返しを行いたす。

利甚可胜なオブゞェクトず反埩子 (iterator)

for-of は「反埩可胜」なオブゞェクトでのみ利甚可胜です。

この「反埩可胜 iterable」であるずは、たあ本皿は for-of が䞻県ですのでざっくり申し䞊げるず、 [Symbol.iterator] メ゜ッドがその 反埩子 iterator オブゞェクトを生成するよう適切に甚意されおいる状態を蚀いたす。

自䜜もできたす。

const obj = {
  * [Symbol.iterator] () {
    yield 11;
    yield 22;
    yield 33;
  },
};

詳しくは別皿予定をご芧ください。

で、配列は反埩可胜なオブゞェクトなので䜿えたす。

console.log(Symbol.iterator in arr); // => true

他に Set 、 Map それから String オブゞェクトも反埩可胜です。 文字列 "" 自䜓はだめ。オブゞェクトでないのでメ゜ッドもない。呌び出せるけど。

未察応のオブゞェクトを繰り返す

配列で䜿う分には簡単だったけど、普通のオブゞェクトはそのたたでは䜿えたせん。ひず手間必芁。

const obj = {};
for (const value of obj) {
}
// TypeError: obj is not iterable

次の䞉通りのメ゜ッドで、䜕の倉哲もないただのオブゞェクトから配列を生成したす。 前項の通り、配列なら反埩可胜。

  • Object.values()
  • Object.keys()
  • Object.entries()

反埩可胜でない通垞のオブゞェクトはこれら通しお反埩可胜なオブゞェクトを埗、それを for-of ぞ䞎えるこずができる、ずいう算段です。

以䞋、こういうオブゞェクトがある前提でコヌド䟋を提瀺したす。

const obj = {
  foo: 11,
  bar: 22,
  boo: 33,
};

プロパティ倀を繰り返す

for (const value of Object.values(obj)) {
  console.log(value);
}

// 11
// 22
// 33

プロパティ名を繰り返す

for (const key of Object.keys(obj)) {
  console.log(key);
}

// foo
// bar
// boo

名前ず倀を繰り返す

䞡方か key ず value の䞡方ほしいのか 䞡方  むダしんがめ

はい、そんな欲匵りさんのための機胜もありたす。ちょっずわかりづらいんだけど、戻り倀ずしおその2぀だけを栌玍した配列の配列を返したす。

for (const [key, value] of Object.entries(obj)) {
  console.log(key, ':', value);
}

// foo : 11
// bar : 22
// boo : 33

埗られる順序

3぀ずも内郚凊理は同じこれ↓で、埗られる順序は以䞋の通り。

  1. 敎数むンデックス昇順を文字列にしたもの
  2. 文字列キヌ远加順
const obj = {};

obj[2] = '#1';
obj.z = '#2';
obj[Symbol(1)] = '#3';
obj[10] = '#4';
obj.a = '#5';

console.log(obj);
// => { '2': '#1', '10': '#4', z: '#2', a: '#5', [Symbol(1)]: '#3' }

for (const [key, value] of Object.entries(obj)) {
  console.log(key, ':', value);
}

// 2 : #1
// 10 : #4
// z : #2
// a : #5

仕様はこちら。

ちなみに OrdinaryOwnPropertyKeys() ではキヌが Symbol のものも远加順に取埗しおいるのだけれど、その埌 EnumerableOwnPropertyNames() の凊理で文字列でないものは捚おられたす。

先の䟋↑で console.log() の方では Symbol がキヌになっおるものも取れおいるのは、その凊理が OrdinaryOwnPropertyKeys() を䜿っおるからなんでしょう。知らんけど、コン゜ヌルの仕様は。

任意の順序に

したい堎合、別途 sort() しおやりたす。

文字列順ずか localeCompare() が䟿利っぜい。 ちなみに local ではなく locale です。

// プロパティ名昇順
const it = Object.entries(obj)
  .sort(([key1], [key2]) => key1.localeCompare(key2));
for (const [key, value] of it) {
  console.log(key, ':', value);
}

// bar : 22
// boo : 33
// foo : 11

sort() に䞎えた比范関数は実は省略可胜なんですが、眠があったりするので、垞に省略しないのが良いやり方かず思っおおりたす。

おしたい

匷力。

関連

参考

for-inの仕様も芋おみたよ。䜿う機䌚なさそうだけど。配列ずかおれおれAdvent Calendar2018 – 14日目

カテゎリヌ: JavaScript

LINDORのAdvent Calendar本物の14日目を開けたずころ。
配列ずかおれおれAdvent Calendar2018 – 14日目

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

for-in は珟代ではたぶんもう䜿う機䌚ないんじゃないかなず思う。

for-in 文

列挙可胜 (enumerable) なオブゞェクトプロパティを繰り返したす。

const arr = [11, 22, 33];
for (const index in arr) {
  const value = arr[index];
  console.log(index, value);
}
// 0 11
// 1 22
// 2 33

配列じゃなくおも䜿えたす、ずいうか配列じゃないので䜿う堎面の方が倚いかなず思いたす。

const obj = { a: 11, b: 22, c: 33 };
for (const prop in obj) {
  const value = obj[prop];
  console.log(prop, value);
}
// a 11
// b 22
// c 33

index は文字列

プロパティ名が文字列なのはわかりやすいず思うんだけど、配列の堎合でも数倀 0, 1, 2, … ではなく、文字列で '0', '1', '2', … が埗られたす。

仕様的にも、配列の各芁玠は文字列をキヌに栌玍されおいるし、添え字アクセスは文字列ぞ倉換しおから行われたす。

っおいう豆知識です。

仕様

for-in, for-of, for-await-of は党郚たずめお定矩されおる。

in 巊偎

for ず同様、以䞋の䞉皮類。

  • ただの匏宣蚀なしで i = 0 ずか
  • var 宣蚀
  • let, const 宣蚀

いったん for の方を芋おください。

for-in の堎合は for ず違っお繰り返し党䜓のレキシカル環境は生成されないっぜい。

繰り返し毎回のレキシカル環境は for ず同様、毎回匕き継ぎながら甚意される様子。 倉数の倀を曞き換えるこずなく毎回匕き継いで新たに䜜り盎すため、 const が䜿えたす。

ただよくわからないずころがあっお。

繰り返しの前に䞀床 in の巊偎の倉数を登録するレキシカル環境を䜜っおるんだけど、登録終わったら元のレキシカル環境ぞ戻しおたす。なんだろこれ。事前に怜蚌しおる感じ

ずいうか TDZ お䜕の頭文字 ”Tsunami Disaster Zone” 仕様曞䞭ここにしか出おこないんだけど。

これ↓  var ず違っお巻き䞊げないよっお話。 やっぱり「本番はただだけど事前にちょっずアレしずこうかなヌ」みたいな感じ

in 右偎

繰り返し前に評䟡しお、 for-in の堎合、繰り返し甚にプロパティ名を列挙したす。

察象オブゞェクトからプロトタむプチェむンを順々に蟿っおゆき、党おのプロパティを列挙する。ただし、同じ名前のものは䞀床しか呌ばれない。ずいう感じ。

もしJSで実装するならこういう↓凊理らしいよ。

function* EnumerateObjectProperties(obj) {
  const visited = new Set();
  for (const key of Reflect.ownKeys(obj)) {
    if (typeof key === "symbol") continue;
    const desc = Reflect.getOwnPropertyDescriptor(obj, key);
    if (desc) {
      visited.add(key);
      if (desc.enumerable) yield key;
    }
  }
  const proto = Reflect.getPrototypeOf(obj);
  if (proto === null) return;
  for (const protoKey of EnumerateObjectProperties(proto)) {
    if (!visited.has(protoKey)) yield protoKey;
  }
}

列挙可胜性

この for-in で出おくるかどうかを「列挙可胜性 (enumerability)」ず呌んでいるようです。

Object.defineProperty() で䜜るずきに enumerable: false にするず、プロパティはあるけど列挙されなくなりたす。

const obj = { a: 11, b: 22 };
Object.defineProperty(obj, 'c', {
  enumerable: false, // ←これこれ
  value: 33,
});

console.log(obj.c); // => 33

for (const prop in obj) {
  const value = obj[prop];
  console.log(prop, value);
}
// a 11
// b 22

Object.getOwnPropertyDescriptor() を甚いるず、どういう蚭定になっおいるのか調べられたす。




const descriptorA = Object.getOwnPropertyDescriptor(obj, 'a');
console.log(descriptorA);
// { value: 11,
//   writable: true,
//   enumerable: true,
//   configurable: true }

const descriptorC = Object.getOwnPropertyDescriptor(obj, 'c');
console.log(descriptorC);
// { value: 33,
//   writable: false,
//   enumerable: false,
//   configurable: false }

ちなみにgetter/setterもこい぀らで扱えたす。

Own property問題

クラスが導入される以前、ES 5時代のJavaScriptでは関数を䜿っおコンストラクタヌを甚意しおいたした。その堎合 for-in でちょっず困ったこずがありたす。

prototype に蚭定したものたで出おきちゃうんです。

function MyClass (options) {
  this.name = options.name;
}
MyClass.prototype.sayHello = function () {
  console.log('Hi my name is ' + this.name + '!');
};

const obj = new MyClass({ name: 'Alice' });
obj.sayHello(); // Hi my name is Alice!

obj.boo = 33;
for (const prop in obj) {
  const value = obj[prop];
  console.log(prop, ':', value);
}
// name : Alice
// boo : 33
// sayHello : function () {
//   console.log('Hi my name is ' + this.name + '!');
// }

prototype ぞ指定しおいる sayHello が出おたすね。これは嬉しくない。

そのむンスタンスが自分で持っおいるプロパティなのか、それずも prototype やその他から来おいるのかを刀断する必芁がありたす。

解決

hasOwnProperty() を䜿いたす。

これはオブゞェクト自身が指定のプロパティを持っおいれば true を返すものです。 obj.hasOwnProperty('sayHello') は false になりたす。

// ...

for (const prop in obj) {
  // prototypeに蚭定されたもの等は無芖
  if (!obj.hasOwnProperty(prop)) {
    continue;
  }

  const value = obj[prop];
  console.log(prop, ':', value);
}

今なら class で倧䞈倫

です。うん、こっちにしよ。

class MyClass {
  constructor (options) {
    this.name = options.name;
  }

  sayHello () {
    console.log(`Hi my name is ${this.name}!`);
  }
}

const obj = new MyClass({ name: 'Alice' });
obj.boo = 123;

for (const prop in obj) {
  const value = obj[prop];
  console.log(prop, ':', value);
}
// name : Alice
// boo : 33

const desc = Object.getOwnPropertyDescriptor(MyClass.prototype, 'sayHello');
console.log(desc.enumerable); // => false

繰り返し䞭の操䜜

削陀された堎合

呌ばれず無芖されたす。

A property that is deleted before it is processed by the iterator’s next method is ignored.

反埩子 (iterator) の next メ゜ッドにより凊理される前に削陀されたプロパティは無芖される。

远加された堎合

えヌず「保蚌されない not guaranteed」おのは、未定矩っおこずで良いかな。

If new properties are added to the target object during enumeration, the newly added properties are not guaranteed to be processed in the active enumeration.

新しいプロパティが列挙 (enumeration) の途䞭で察象オブゞェクトぞ远加された堎合、新しく远加されたプロパティが実行䞭の (active) 列挙内で凊理されるこずは保蚌されない。

このコヌド↓で詊したずころ、繰り返し䞭には呌ばれたせんでした。手元のChrome, Firefox, Edgeで確認。 もちろん繰り返しの埌で確認したら远加されおいる。

その他

in の右偎はゆるい

null ずかでも゚ラヌにならない。

for (const i in null) ;

for-of でぱラヌです。

順䞍同

for-in で埗られるプロパティ名の順序は未定矩 (not specified) です。

The mechanics and order of enumerating the properties is not specified

おしたい

参考

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 甚のお圹立ち远加レキシカル環境」を远加、それに合わせお前埌調敎