JavaScriptの勉強中:その4 Arrays

よしやるかー。先に言っておくと Arrays だけで力尽きた。

bufferings.hatenablog.com

## Ch. 7 Arrays

  • 32-bit index なので index の範囲は 0 から 4,294,967,294 まで
> let a = []
undefined
> a[4294967294] = 1
1
> a.length
4294967295
> a[4294967295] = 2
2
> a.length
4294967295
> a
[ <4294967294 empty items>, 1, '4294967295': 2 ]

へー。4294967295はキーになっちゃったし length も変わらないのか。面白いな。

### 7.1 Creating Arrays

Array Literal []

  • カンマだけ書いて値を書かなかったら間の要素が抜けた sparse array になる。その要素を参照しようとすると undefined になる
> let count = [1,,3]
undefined
> count
[ 1, <1 empty item>, 3 ]
> count[1]
undefined
  • ちなみに Array literal は末尾のカンマを許可してるので [,,] の length は3じゃなくて2
  • ES6 以降では spread operator が利用できる
> let a = [1,2,3];
undefined
> [0, ...a, 4]
[ 0, 1, 2, 3, 4 ]
  • spread operator は iterable なオブジェクトに対して利用できる。ので、文字列を渡すとこうなる:
> [..."abcde"]
[ 'a', 'b', 'c', 'd', 'e' ]

new Array(), Array.of(), Array.from()

  • 配列の生成には Array() コンストラクターも使用できる。けど、引数の数で動きが変わるのが危なそうなので、使わないだろうな。
  • new Array() はそんな感じなので ES6 で Array.of() が導入された。けどリテラルで良さそう。
  • ES6 で Array.from() も導入された。これは iterable や array-like なオブジェクトから配列を生成できる。
    • iterable オブジェクトの場合は、これと同じ [...iterable]
    • array-like オブジェクトの場合は array-like から本当の array を作成するのに便利(array-like オブジェクトは、Array じゃないけど数値の length プロパティがあって、その長さ分の数値のプロパティを持ってるオブジェクトのこと)
    • オプショナルで2つ目の引数として関数を渡すことができて要素に変換をかけることができる。 map みたいな感じ。

### 7.2 Reading and Writing Array Elements

  • 232-1 より小さい非負の整数のプロパティ名を指定したら、自動的に length プロパティが更新される。あー、これが今日のはじめで length が更新されなかったやつか。

### 7.4 Array Length

  • 現在の length の値以上の index に値を設定した場合には length がその index + 1 の値に更新される
  • length に現在の値よりも小さな値(非負)を設定したら、その値以上の要素は削除される

### 7.6 Iterating Arrays

  • ES6以降では for/of ループで処理を行うことができる
> for(const v of [..."Hello World"]) {
... console.log(v);
... }
H
e
l
l
o
 
W
o
r
l
d
  • index を使用したい場合には entries() を使う
> for(const [i, v] of [..."Hello World"].entries()) {
... console.log(`${i}:${v}`);
... }
0:H
1:e
2:l
3:l
4:o
5: 
6:W
7:o
8:r
9:l
10:d
  • forEach() もある
> let uppercase = "";
undefined
> [..."Hello World"].forEach(c => uppercase += c.toUpperCase())
undefined
> uppercase
'HELLO WORLD'
  • forEach() は第二引数で index を受け取ることもできる
> [..."Hello World"].forEach((v,i) => console.log(`${i}:${v}`))
0:H
1:e
2:l
3:l
4:o
5: 
6:W
7:o
8:r
9:l
10:d
  • forEach()for/of ループと違って sparse array の存在しない要素に対しては呼び出されない
// for/of
> for(const v of [1,,,4]){
... console.log(v);
... }
1
undefined
undefined
4

// forEach()
> [1,,,4].forEach(v => console.log(v));
1
4

ん? undefined を要素として入れたらどうなるんだろう?

> [1,2,undefined,4].forEach(v => console.log(v));
1
2
undefined
4

へー。その場合は処理されるのか。 sparse array の要素がないことの表現としての undefined と実際に要素として設定された undefined は区別されてるんだね。

  • あとは普通の for loop ももちろん使用可能

### 7.8 Array Methods

forEach()

  • 途中でイテレーションを終了させる方法はない。つまり普通の for ループの break に相当する仕組みはない。
  • さっきも書いたけど sparse array の存在しない要素に対しては呼び出されない

map()

  • sparse array の場合は存在しない要素に対しては呼び出されないが、返される配列も sparse になる。
> [1,,,4].map(v => v*2);
[ 2, <2 empty items>, 8 ]

filter()

  • sparse array の存在しない要素はスキップされる
> [1,,,4].filter(v => v > 1)
[ 4 ]
  • なので、 sparse array の隙間をなくしたかったらこんな感じにかける:
> [1,,,4].filter(() => true)
[ 1, 4 ]

find() findIndex()

  • 最初にみつかった要素、またはインデックスを返す
  • 見つからなかった場合、 find()undefinedfindIndex()-1 を返す

every() some()

  • every() は全ての要素が条件を満たした場合に true を、それ以外の場合は false を返す。ひとつでも条件を満たさないものがあったら false を返すので、空の配列だと true になる。
  • some() は少なくとも1つの要素が条件を満たした場合に true を、それ以外の場合は false を返す。ひとつでも条件を満たすものがあったら true を返すので、空の配列だとfalse になる。
> [].every(v => v > 0 )
true
> [].some(v => v > 0 )
false

reduce() reduceRight()

  • reduce() は左から、reduceRight() は右から要素を処理して reduce する
> [1,2,3,4,5].reduce((acc,cur) => acc+cur, 0)
15

初期値を省略したら最初の要素が初期値になる。

> [1,2,3,4,5].reduce((acc,cur) => acc+cur)
15

この場合、最初の呼び出しで1番目と2番目の要素が渡されるので、1回処理が減る。

// 初期値を指定する場合
> [1,2,3,4,5].reduce((acc,cur) => {console.log(`(${acc}, ${cur})`); return acc+cur;}, 0)
(0, 1)
(1, 2)
(3, 3)
(6, 4)
(10, 5)
15

// 初期値を指定しない場合
> [1,2,3,4,5].reduce((acc,cur) => {console.log(`(${acc}, ${cur})`); return acc+cur;})
(1, 2)
(3, 3)
(6, 4)
(10, 5)
15
  • reduce で表現された処理はすぐ複雑になって理解するのが難しくなるから、普通のループを使う方が読み書きしやすいかも

flat() flatMap()

  • ES2019 で導入された
  • flat() は配列をフラットにできる。引数なしだと1段階。引数でフラット化のレベルを指定できる
  • flatMap()map の結果をフラット化できる

concat()

  • 配列をつなげることができる
  • 配列の要素は展開される(再帰的には展開されない)
> let a = [1,2,3]
undefined

// 複数要素を追加
> a.concat(4,5)
[ 1, 2, 3, 4, 5 ]

// 配列は展開される
> a.concat([4,5],[6,7])
[
  1, 2, 3, 4,
  5, 6, 7
]

// 配列の配列は展開されない
> a.concat(4,[5,[6,7]])
[ 1, 2, 3, 4, 5, [ 6, 7 ] ]

// 元の配列は変更されない
> a
[ 1, 2, 3 ]

spread operator で良さそうかな。

push() pop() shift() unshift()

  • push() pop(): array を stack のように扱うことができる
  • push() には複数の要素を渡すことができるけど concat() と違って配列は展開されない
  • unshift() shift(): array の末尾ではなく先頭に対して処理を行う
  • push()shift() を使うと queue のように扱うことができる
  • unshift() に複数の要素を渡すと前から順番に追加されるのではなくて、一気に追加されることに注意
// 順番に追加した場合
> let a = []
undefined
> a.unshift(1)
1
> a.unshift(2)
2
> a
[ 2, 1 ]

// 一気に追加
> a = []
[]
> a.unshift(1,2)
2
> a
[ 1, 2 ]

slice()

  • 指定された配列のスライスを返す。
  • 最初の引数が開始インデックスで、2番目の引数が終了インデックス。終了インデックスは含まれない。
  • 開始インデックスだけを渡すと、そこから最後まで
  • 負のインデックスを渡すと length からの相対的な位置になる。つまり -1 は最後の要素。
  • 元の配列は変更されない

splice()

  • 配列に要素を追加したり、配列から要素を削除したりする。 slice() と名前が似てるけど全然違う動きをするから注意。
  • slice()concat() と違って元の配列を変更する。
  • 個人的には引数が分かりにくいし、あんまり使いたくないかなぁ。使うときは調べてから使おっと。

fill()

  • 配列を指定した値で埋める
  • 元の配列を変更する
  • オプショナルな2番目と3番目の引数はそれぞれ開始インデックスと終了インデックス(含まない)。

copyWithin()

  • 配列のスライスをその配列の新しい場所にコピーする
  • 元の配列を変更する
  • これも頭の片隅に覚えておくぐらいにしといて、使うときに調べよっと

indexOf() lastIndexOf()

  • それぞれ前からと後ろからと探してインデックスを返す
  • 見つからなかったら -1 を返す
  • 比較には === を使用する
  • オブジェクトの場合は参照が一致するかを見るので、内容を見たい場合は find() を使って自分で作った predicate を渡せばいい
  • 開始インデックスを指定することもできる
  • 文字列にも indexOf()lastIndexOf() があって array のメソッドと似てるんだけど、開始インデックスに負の値を指定しても 0 として扱われる点に注意

includes()

  • ES2016
  • 配列が指定された値を含む場合は true を、そうでない場合は false を返す
  • 比較には SameValueZero を使用するため NaN をチェックすることができる

等価性の比較と同一性 - JavaScript | MDN

sort()

  • 元の配列を並び替える
  • 何も指定しなかったらアルファベット順に並び替える。 undefined は最後になる
  • 比較の関数を指定することができる

reverse()

  • 元の配列を逆順に書き換える

join() toString()

  • 文字列に変換するメソッド。ログやエラーメッセージ用に使うことができる
  • あとで使うためにシリアライズしたりしたい場合はこれらのメソッドではなく JSON.stringify() を使う
  • join(): 引数でセパレーターを指定して結合できる。引数なしだとカンマでつなぐ。
  • toString(): join() の引数なしバージョンと同じ

出力内容には[] とかはないことに注意

> [1,2,3].toString()
'1,2,3'
> ["a","b","c"].toString()
'a,b,c'
> [1,[2,"c"]].toString()
'1,2,c'

Array.isArray()

  • static method
  • 配列かどうかをチェックする

7.9 Array-Like Objects

  • length プロパティがあって、それに対応する非負の整数をプロパティとして持っているオブジェクト
  • 例えば document.querySelectorAll() は array-like オブジェクトを返す
  • for ループで配列のようにイテレートすることができるし、配列のメソッドを適用したりすることができる
> let a = {"0": "a", "1": "b", "2": "c", length: 3}
undefined
> Array.prototype.join.call(a)
'a,b,c'
> Array.prototype.map.call(a, x => x.toUpperCase())
[ 'A', 'B', 'C' ]
  • Array.from() で true array に変換できる

今日はここまでー。次は Functions だなー。先は長い。