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

カテゎリヌ: JavaScript

スプレッド挔算子  じゃなくお、スプレッド構文の䜿える堎所ずか䜿い方ずかそういう。配列ずかおれおれAdvent Calendar2018 – 20日目

カテゎリヌ: JavaScript

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

... を䜿うず配列から配列を䜜るのが簡単です。

const arr1 = [11, 22, 33];
const arr2 = [44, 55];

const arr3 = [...arr1, ...arr2];
console.log(arr3); // => [ 11, 22, 33, 44, 55 ]

const arr4 = [0, ...arr1, 0, ...arr2, 0];
console.log(arr4); // => [ 0, 11, 22, 33, 0, 44, 55, 0 ]

䜿い方

次の個所で利甚可胜です。

  • 配列初期化子 []
  • オブゞェクト初期化子 {}
  • 関数呌び出し時の匕数 ()
  • 分割代入 = 、関数の仮匕数 ()

配列初期化子 []

... に続けお反埩可胜 (iterable) なオブゞェクトを眮きたす。たあ普通は配列ですね。

const arr1 = [22, 33];
const arr2 = [55, 66];
const arr = [11, ...arr1, 44, ...arr2, 77];
console.log(arr);
// [ 11, 22, 33, 44, 55, 66, 77 ]

初期化䞭の配列芁玠ずしお ... を䌎ったものを芋぀けるず、内郚凊理 GetIterator() を通しお [Symbol.iterator]() メ゜ッドを甚いお反埩し、芁玠を远加したす。

反埩可胜オブゞェクト

反埩子を埗られれば動くので、必ずしも配列でなくおも構いたせん。

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

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

オブゞェクト初期化子 {}

オブゞェクトも ... で耇補できたす。 Object.assign() よりらくちん。

const obj1 = { b: 22, c: 33 };
const obj2 = { e: 55, f: 66 };
const obj = { a: 11, ...obj1, d: 44, ...obj2, g: 77 };
console.log(obj);
// { a: 11, b: 22, c: 33, d: 44, e: 55, f: 66, g: 77 }

内郚凊理 CopyDataProperties() を甚いお ... 右のオブゞェクトのプロパティをコピヌしおいきたす。

玔粋オブゞェクト以倖も䜿えたす。

配列

䜿えたす。むンデックスがプロパティ名になりたす。

const arr1 = [11, 22];
const obj = { ...arr1 };
console.log(obj);
// { '0': 11, '1': 22 }

Symbol をプロパティ名に持぀オブゞェクト

䜿えたす。普通に耇補されたす。

const obj1 = { [Symbol('hey')]: 33 };
const obj = { ...obj1 };
console.log(obj);
// { [Symbol(hey)]: 33 }

継承しおきたプロパティ

は远加されたせん。

䜕か new しお䜜ったオブゞェクトで䜿える、 prototype から持っおきおる系メ゜ッドが远加されちゃったりしないわけですね。䟿利。

const obj1 = Object.create({ inherited: 11 });
obj1.own = 22;
console.log(obj1.inherited); // => 11
console.log(obj1.own); // => 22

const obj = { ...obj1 };
console.log(obj); // => { own: 22 }
console.log(obj.inherited); // => undefined

非オブゞェクト

無芖されたす。

const obj = { ...123 };
console.log(obj);
// {}

undefined か null の堎合、内郚凊理 CopyDataProperties() の過皋で単玔に無芖されたす。

それ以倖、真停倀、数倀、文字列、シンボルの堎合、内郚凊理 ToObject() を通しお察応するコンストラクタヌ䟋えば String の新芏オブゞェクトが䜜成されるんだけど、新しいオブゞェクトは圓然自身のプロパティを䞀切持っおいないので、䜕も远加されたせん。

ちなみに内郚凊理 ToObject() ぞ undefined か null を䞎えるず、 TypeError になっちゃう。

関数呌び出し時の匕数

関数を䜜る際ではなく呌び出す方ね。

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

配列初期化子 [] の ... ず同様、内郚凊理 GetIterator() を甚いお反埩、匕数リストを䜜成しお、関数呌び出しを実行したす。

仮匕数を ... で受け取っお、それをそのたた他の関数ぞパスする、みたいな䜿い方が良さそう

分割代入、関数の仮匕数

分解しお䞎えるんじゃなくお、䞎えられたものを分解し、か぀たずめお倉数の倀ずしお受け取るもの。

const cols = ['Taro', 'Yamada', 199, 99.9];
const [name, ...rest] = cols;
const exec = (cmd, ...options) => {}
exec('goStraight', 50);
exec('moveTo', 10, 20);

分割代入はたた埌日やりたす。

その他

「スプレッド挔算子」ではない

... は特定の曞匏でしか利甚できない構文 (syntax) の䞀郚です。

適圓な堎所で適圓に䜿うず構文゚ラヌになりたす。

const foo = [] + ...[];
// SyntaxError: Unexpected token ...

たあおれも去幎は「スプレッド挔算子」ず呌んでたけどね

あずMDNも前は「スプレッド挔算子」蚀っおたはず。気が付いたら倉わっおた。

「スプレッド構文」もない

実は ... を甚いた各皮構文の䞀郚であっお、 ... 単䜓には名前は付いおないみたいです。括匧 () に括匧 (parentheses) 以䞊の名前がないのず同様。

ただ配列初期化子 [] の構文においおは SpreadElement ずいう名前の、えヌず䜕おいうの、個所、で ... が利甚されおたす。ただこれも ... だけじゃなくお ...AssignmentExpression 党䜓で SpreadElement になるので、やっぱり ... 自䜓の名前はないですね。

実は仕様曞䞭にも “spread” ずいう単語はそんなに出おきおないです。

Chromeで仕様曞から怜玢した様子。
“spread” で怜玢しおヒットは37件のみ。

ずはいえ、英単語 spread が持぀雰囲気のひず぀は「折りたたたれたものを広げる」ずいう感じだそうなので、たあぎったりですね。MDNでも “Spread syntax” だし、他の人たちもそう呌んでるし、これでいいよね。IIFE即時実行関数みたいなもんか。そうか

仕様曞䞭ではあず他に、文法 (Grammar) の章で句読点 (punctuator) のひず぀ずしお玹介されおいるが。パヌザヌ䜜るずきに必芁な知識なのかな、よくわからない。

デフォルトコンストラクタヌ

継承はしたけどコンストラクタヌを甚意しおいないクラスでは、 ... を䜿ったこんなコンストラクタヌが自動的に甚意されるようです。

constructor(... args){ super (...args);}

なんかスペヌスの眮き方独特だな

concat() ず連結展開可胜性

単語 “spread” の数少ない出珟個所のひず぀に IsConcatSpreadable() ずいう内郚凊理がある。

配列の concat() からのみ呌ばれる内郚凊理。

concat() はこの内郚凊理を利甚しお、プロパティ [Symbol.isConcatSpreadable] を参照しお、 true であれば、匷制的に配列ずみなしお展開、察象配列ぞ連結するずいうもの。逆に false であれば匷制的に展開なしに連結したす。初期倀はないので普通は undefined で、その堎合は配列かどうかで刀断されたす。

䟋ずしお、たずは配列颚のオブゞェクト。配列ではないので、オブゞェクト䞞ごずが芁玠になりたす。

const obj = {
  0: 11,
  1: 22,
  2: 33,
  length: 3,
};
const arr = [0];
const arr2 = arr.concat(obj);
console.log(arr2);
// [ 0, { '0': 11, '1': 22, '2': 33, length: 3 } ]

続いお [Symbol.isConcatSpreadable] を蚭定したもの。無事、本物の配列のように連結されたした。

const obj = {
  0: 11,
  1: 22,
  2: 33,
  length: 3,
  [Symbol.isConcatSpreadable]: true,
};
const arr = [0];
const arr2 = arr.concat(obj);
console.log(arr2);
// [ 0, 11, 22, 33 ]

逆に普通の配列むンスタンスで [Symbol.isConcatSpreadable] に false を蚭定するず、展開されず配列䞞ごずが察象配列の芁玠になりたす。二重配列。

スプレッド構文関係ないけど、せっかくなのでここで。

おしたい

そこたで ... を頻繁に䜿うかずいうずそうでもない気もするんだけど、でもこれがあるずめっちゃ楜になる堎面があるので、この仕様䜜っおくれたひずありがずう、ずいう気持ちです。

関連

参考

反埩凊理の䞭身、IterableずIteratorに぀いお仕様曞を調べおみた。配列ずかおれおれAdvent Calendar2018 – 19日目

カテゎリヌ: JavaScript

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

ここたで䜕床か出おきた反埩可胜 (iterable) ず反埩子 (iterator) のお話です。やっず。

先にたずめ

  • Iterableオブゞェクトは
    • Iteratorを返すメ゜ッド [Symbol.iterator] を持぀
    • for-of で䜿える
  • Iteratorオブゞェクトは
    • IteratorResultを返すメ゜ッド next() を持぀
  • IteratorResultオブゞェクトは
    • プロパティ done, value を持぀
  • 配列は
    • Iterableである
    • Iteratorではない
  • arr.values() は
    • Iterableである
    • Iteratorである
  • Generatorオブゞェクトは
    • Iterableであり、か぀Iteratorである
  • Generator関数は
    • function*(){} のや぀
    • Generatorオブゞェクトを返す

自䜜䟋

// Iteratorはnext()を持぀
class MyIterator {
  constructor () {
    this.index = 0;
    this.values = [11, 22, 33];
  }

  next () {
    const value = this.values[this.index];
    this.index += 1;
    const result = { done: !value, value };
    return result;
  }
}

// Iterableは[Symbol.iterator]()を持぀
class MyIterable {
  [Symbol.iterator] () {
    return new MyIterator();
  }
}

const iterable = new MyIterable();
for (const value of iterable) {
  console.log(value);
}
// 11
// 22
// 33

反埩 (iteration)

IterableずIteratorは、実圚するコンストラクタヌではなく抂念的な「むンタヌフェむス」ずしお定矩されおいたす。仕様だけが瀺されお、それを満たせば「これはIterableである」ずかなんずか蚀っお良いずいうこずです。

仕様曞のIterationの章は、むンタヌフェむスに぀いおの説明から始たりたす。

An interface is a set of property keys whose associated values match a specific specification. Any object that provides all the properties as described by an interface’s specification conforms to that interface. An interface is not represented by a distinct object. There may be many separately implemented objects that conform to any interface. An individual object may conform to multiple interfaces.

  • むンタヌフェむスずは、関連する倀が特定の仕様に合臎するプロパティキヌの組み合わせである。
  • むンタヌフェむスの仕様で述べられるすべおのプロパティを提䟛するあらゆるオブゞェクトは、そのむンタヌフェむスに準拠しおいる。
  • むンタヌフェむスは個別のオブゞェクトずしおは存圚しない。
  • あるむンタヌフェむスに準拠する、別々に実装されおいる耇数のオブゞェクトがあっおもよい。
  • 個々のオブゞェクトが耇数のむンタヌフェむスに準拠しおもよい。

※蚳泚: 勝手に1文ごずに箇条曞きぞ倉換したした。内容はそのたた。

ちなみに isIterable() や isIterator() のようなものは仕様にはありたせん。たあメ゜ッド実行しお戻り倀が正しいこずたで確認しないずいけないので、JavaScriptで綺麗に぀くるのはちょっず難しいっすかね。

定矩されおいるむンタヌフェむス

5皮ありたす。

  • 反埩可胜 Iterable
  • 反埩子 Iterator
  • 非同期反埩可胜 AsyncIterable
  • 非同期反埩子 AsyncIterator
  • 反埩結果 IteratorResult

反埩可胜 (Iterable)

[Symbol.iterator]() メ゜ッドが反埩子を返すもの。

for-of で䜿えるや぀。配列ずか、 Set ずか、配列ずか、文字列ずか、あず配列ずか。

仕様が短い。

Property Value Requirements
@@iterator A function that returns an Iterator object. The returned object must conform to the Iterator interface.

プロパティ 倀 芁求
@@iterator Iterator オブゞェクトを返す関数。 返华されるオブゞェクトは Iterator むンタヌフェむスに準拠しなければならない。

[Symbol.iterator]() メ゜ッド

仕様曞では @@iterator ずいう圢で衚珟される名前のメ゜ッドです。特別なものなので、名前が文字列ではなくシンボルになっおいたす。知らんけど。

この [Symbol.iterator]() ずいう名前でIteratorを返すオブゞェクトをIterableず呌べるずいう話だけど、それ以倖のメ゜ッドがIteratorを返すのは自由です。䟋えば配列はこの [Symbol.iterator]() メ゜ッドを持ちたすが、他にも values() ずいうIteratorを返すメ゜ッドも持っおいたす。

ちなみに arr[Symbol.iterator] === arr.values です。

実装䟋

Iteratorが䜕かは次項に譲り぀぀、それを返す [Symbol.iterator]() メ゜ッドを持぀オブゞェクトのクラスです。

class MyIterable {
  [Symbol.iterator] () {
    return new MyIterator();
  }
}

反埩子 (Iterator)

next() メ゜ッドを持ち、それを䜿っお反埩できるもの。

Object.values() ずか。for-of を for に分解したずきに出おくるや぀。

Property Value Requirements
next A function that returns an IteratorResult object. The returned object must conform to the IteratorResult interface. If a previous call to the next method of an Iterator has returned an IteratorResult object whose done property is true, then all subsequent calls to the next method of that object should also return an IteratorResult object whose done property is true. However, this requirement is not enforced.

Note 1

Arguments may be passed to the next function but their interpretation and validity is dependent upon the target Iterator. The for-of statement and other common users of Iterators do not pass any arguments, so Iterator objects that expect to be used in such a manner must be prepared to deal with being called with no arguments.

プロパティ 倀 芁求
next IteratorResult オブゞェクトを返す関数。 返华されるオブゞェクトは IteratorResult むンタヌフェむスに準拠しなければならない。もし Iterator の前回の next メ゜ッド呌び出しが返した IteratorResult の done プロパティが true である堎合、以降の next メ゜ッド呌び出しが返す IteratorResult オブゞェクトも done プロパティが true になるだろう (should) 。ただし、この芁求は匷制されない。

ノヌト1

匕数を next 関数ぞ䞎えおも良いが、それらの解釈や劥圓性は察象 Iterator に䟝る。 for-of 構文やその他の䞀般的な Iterator の利甚者は匕数を䜕も䞎えないため、そのような䜜法での利甚が想定される Iterator オブゞェクトは、匕数なしで呌ばれた堎合も察凊するようになっおいなければならない。

実装䟋

内包する倀が固定だけど。

class MyIterator {
  constructor () {
    this.index = 0;
    this.values = [11, 22, 33];
  }

  next () {
    const value = this.values[this.index];
    this.index += 1;
    const result = { done: !value, value };
    return result;
  }
}

任意のプロパティ

next() は必須だけど、他に return() ず throw() を実装しおも良いそうです。

Property Value Requirements
return A function that returns an IteratorResult object. The returned object must conform to the IteratorResult interface. Invoking this method notifies the Iterator object that the caller does not intend to make any more next method calls to the Iterator. The returned IteratorResult object will typically have a done property whose value is true, and a value property with the value passed as the argument of the return method. However, this requirement is not enforced.
throw A function that returns an IteratorResult object. The returned object must conform to the IteratorResult interface. Invoking this method notifies the Iterator object that the caller has detected an error condition. The argument may be used to identify the error condition and typically will be an exception object. A typical response is to throw the value passed as the argument. If the method does not throw, the returned IteratorResult object will typically have a done property whose value is true.

Note 2

Typically callers of these methods should check for their existence before invoking them. Certain ECMAScript language features including for-of, yield*, and array destructuring call these methods after performing an existence check. Most ECMAScript library functions that accept Iterable objects as arguments also conditionally call them.

プロパティ 倀 芁求
return IteratorResult オブゞェクトを返す関数。 返华されるオブゞェクトは IteratorResult むンタヌフェむスに準拠しなければならない。このメ゜ッドの呌び出しは Iterator オブゞェクトぞ、呌び出し偎がこれ以䞊 next メ゜ッド呌び出しを行う意図がないこずを通知する。返华される IteratorResult の done プロパティは、䞀般に true になり、 value プロパティは return メ゜ッドの匕数に䞎えられた倀ずなる。ただし、この芁求は匷制されない。
throw IteratorResult オブゞェクトを返す関数。 返华されるオブゞェクトは IteratorResult むンタヌフェむスに準拠しなければならない。このメ゜ッドの呌び出しは Iterator オブゞェクトぞ、呌び出し者が゚ラヌ状態を怜出したこずを通知する。匕数ぱラヌ状態特定のために䜿っおもよく、たた倚くの堎合は䟋倖オブゞェクトになる。代衚的な目的は匕数ずしお枡された倀を throw するこずである。このメ゜ッドが throw しない堎合、返华される IteratorResult の done プロパティは、䞀般に true になる。

ノヌト2

䞀般的にこれらのメ゜ッドの呌び出し偎は、その存圚を確認しおから呌び出すこずになるだろう (should) 。 for-of や yield* 、配列の分解 (destructuring) ずいった、信頌できるECMAScript蚀語の機胜は存圚確認を実行した埌にこれらのメ゜ッドを呌び出しおいる。 Iterable オブゞェクトを受け付けるほずんどのECMAScriptラむブラリヌ関数もたた、条件次第でこれらを呌ぶようになっおいる。

反埩可胜な反埩子

ECMAScriptでIteratorずしお蚘述されおいるものは、いずれも %IteratorPrototype% ずいう秘密のプロトタむプを継承するようになっおいたす。

このプロトタむプは単に this を返すだけの [Symbol.iterator]() を持぀ずされたす。

぀たり暙準のIteratorは反埩可胜であり、Iteratorが䜜るIteratorは自身である (it === it[Symbol.iterator]()) ずいうわけですね。

ず、そうわけで配列はもちろんのこず、配列から明瀺的に生成したIteratorもたた for-of で䜿えるのです。

const arr = [11, 22, 33];

for (const value of arr) {
  console.log('arr', value);
}
// arr 11
// arr 22
// arr 33

const it = arr.values();
for (const value of it) {
  console.log('it', value);
}
// it 11
// it 22
// it 33

非同期の反埩可胜、反埩子

for-await-of で䜿えるや぀。

基本的に同期のものず同じっぜい。

違うのは、 next() が返すものが「 IteratorResult オブゞェクト」から「 IteratorResult オブゞェクトのプロミス」になっおる点。

反埩結果 (IteratorResult)

いろいろ曞いおあるけど、持っおるのは倀だけなんで、実際そんなにややこしくはないね。

Property Value Requirements
done Either true or false. This is the result status of an iterator next method call. If the end of the iterator was reached done is true. If the end was not reached done is false and a value is available. If a done property (either own or inherited) does not exist, it is consider to have the value false.
value Any ECMAScript language value. If done is false, this is the current iteration element value. If done is true, this is the return value of the iterator, if it supplied one. If the iterator does not have a return value, value is undefined. In that case, the value property may be absent from the conforming object if it does not inherit an explicit value property.

プロパティ 倀 芁求
done true か false 。 これは iterator の next メ゜ッド呌び出しの結果状態である。iteratorが最埌たで到達しおいれば done は true になる。iteratorが最埌たで到達しおいなければ done は false になり、 value は有効になる。 done プロパティ自身のものでも継承したものでもが存圚しない堎合は false 倀を持぀ものずみなす。
value ECMAScript蚀語の任意の倀。 done が false の堎合、これは珟圚の反埩芁玠の倀である。 done が true の堎合、これは iterator の戻り倀である。あれば。 iterator が戻り倀を持たない堎合、 value は undefined になる。その堎合、確認䞭のオブゞェクトが明確な value プロパティを継承しおいなければ value プロパティはそのオブゞェクトから欠萜しおもよい (may) 。

ゞェネレヌタヌ

反埩子でありながら反埩可胜であるもの

A Generator object is an instance of a generator function and conforms to both the Iterator and Iterable interfaces.

Generator instances directly inherit properties from the object that is the value of the prototype property of the Generator function that created the instance. Generator instances indirectly inherit properties from the Generator Prototype intrinsic, %GeneratorPrototype%.

Generatorオブゞェクトはgenerator関数のむンスタンスであり、 Iterator ず Iterable むンタヌフェむスの䞡方に準拠するものである。

Generatorむンスタンスはそのむンスタンスを生成したGenerator関数の prototype プロパティの倀であるオブゞェクトから盎接的にプロパティを継承する。Generatorむンスタンスは固有のGeneratorプロトタむプ、%GeneratorPrototype%から非盎接的にプロパティを継承する。

Generator関数 (function*(){}) の正䜓がわかりたしたね

function* f () {
  yield 1;
}
f.prototype.sayHello = () => console.log('Hello!');

const it = f();
it.sayHello();
// Hello!

for-of ずIterable

GetIterator() ずいう内郚凊理があっお、こい぀で of 右偎のオブゞェクトから䟋の [Symbol.iterator]() を通しおIteratorを取埗しおたす。取埗できなければ TypeError 。

第3匕数に任意の method を枡せるようになっおるけど、仕様曞芋た感じ党郚 @@iterator か @@asyncIterator みたい。

その他

Object.values() vs arr.values()

Object.values(obj) はただの配列を返しお、その䞭身が obj のプロパティ倀になりたす。

arr.values() は arr の項目を順に返すIteratorを返したす。

おたけ: 英単語 iteration vs repetition

反埩ず繰り返し、どちらも同じような意味だけど、どう違うんだろうか。

これの回答がわかりやすい。合っおるのかどうかは刀断しかねるが。

Iteration uses an iterator to count through a list of (usually different) items:

Today, I must:
1: go shopping
2: clean the house
3: mow the lawn

Repetition does the same thing again, and again, etc…:

One sheep, two sheep, three sheep, …

挫画の単行本を続けお読むのが iterate で、同じ本を䜕床も読むのが repeat おな感じみたい。

おしたい

ほずんどただの翻蚳になっおしたった。

たあ仕様曞が䞀番わかりやすいから仕方ない。

関連

参考

配列で重耇する項目を取り陀くや぀4皮。配列ずかおれおれAdvent Calendar2018 – 18日目

カテゎリヌ: JavaScript

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

ありそうでないメ゜ッド、それが重耇排陀です。

Underscore.jsやLodashには uniq() おのがあっお、こんな感じ↓で䜿えたす。

const arr0 = [11, 22, 11, 22, 33];
const arr1 = _.uniq(arr0);

JavaScriptにはありたせん。

Set を䜿う

単玔な倀ならこれが䞀番簡単。

Set は重耇しない倀を栌玍できるオブゞェクトです。 ... で展開しお配列オブゞェクトぞ再倉換したす。

const arr0 = [11, 22, 11, 22, 33];
const arr1 = [...new Set(arr0)];
console.log(arr1); // => [ 11, 22, 33 ]

includes() で確認する

これが䞀番玠盎。

includes() は配列が指定の芁玠を含むかどうか調べるや぀です。 重耇しおなかったら远加するだけ。

const arr0 = [11, 22, 11, 22, 33];

const arr1 = arr0.reduce((a, v) => {
  if (!a.includes(v)) {
    a.push(v);
  }
  return a;
}, []);

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

reduce() に自信がなければ forEach() でも。

const arr1 = [];
arr0.forEach((v) => {
  if (!arr1.includes(v)) {
    arr1.push(v);
  }
});

some() でやる

Set も includes() もオブゞェクトの堎合は完党に同じむンスタンスでないず反応しないので、その堎合は some() がよろしいかず。

もっず自由床が高い。

const arr0 = [
  { id: '11', num: 1 },
  { id: '22', num: 2 },
  { id: '11', num: 3 },
  { id: '22', num: 4 },
  { id: '33', num: 5 },
];

const arr1 = arr0.reduce((a, v) => {
  if (!a.some((e) => e.id === v.id)) {
    a.push(v);
  }
  return a;
}, []);

console.log(arr1);
// [ { id: '11', num: 1 },
//   { id: '22', num: 2 },
//   { id: '33', num: 5 } ]

重耇分は先に出おくるや぀が優先です。

Map を䜿う

これもオブゞェクトに察応。初芋で「ぎょえヌ」おなりそう。

const arr0 = [
  { id: '11', num: 1 },
  { id: '22', num: 2 },
  { id: '11', num: 3 },
  { id: '22', num: 4 },
  { id: '33', num: 5 },
];

const arr1 = [...new Map(arr0.map((v) => [v.id, v])).values()];

console.log(arr1);
// [ { id: '11', num: 3 },
//   { id: '22', num: 4 },
//   { id: '33', num: 5 } ]

仕組みは Set ず some() の組み合わせです、だいたい。

  1. map() でIDずオブゞェクト本䜓の組み合わせぞ倉換
  2. 組み合わせを元に Map オブゞェクトを䜜成。ここでID Map のキヌが重耇するものは排陀
  3. values() でオブゞェクト Map の倀のみの反埩子を埗る
  4. スプレッド構文 ... を䌎う配列初期化子 [] で配列に。完成

重耇分は埌ろにあるや぀が優先䞊曞きです。

おしたい

なんか良いのある

参考

だいたいの繰り返しは配列の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 でもできるようにしおおこう、ずかそういう感じなのかなあ。

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

関連

参考