これは N予備校プログラミングコース Advent Calendar 2021 18 日目の記事です
皆さんこんばんは、すライムです。
初めましての方は初めまして。
N予備校の、しがない生徒のはしくれです。
世間はすっかり寒くなってきましたね。
まだまだ外出もはばかられますし、最近はお風呂だけが楽しみです。
わが家にはノーネクタイ風呂(ノーネクタイで入るお風呂)があるのですが、
最近、シャワーヘッドを節水機能付きのものに交換しました。
さて、今年のアドベントカレンダーでは、
私が JavaScript を学ぶ中で
「何が分からないのかが分からない」
と、思うことがいくつか有りましたので、
そのうちのひとつを深夜のテンションで書いてみたいと思います。
動機
JavaScript って、数値には数値用の関数があったり、
文字列には文字列用の関数があったりしますよね?
初学者の私には、配列用の関数あたりから、
少し、勝手が違う印象でしたので、
その時の違和感や経験を共有したいと思いました。
関数と戻り値
関数にもいろいろありますが...
JavaScript に最初から用意されている、
「標準組み込み関数」について書いています。
この記事では単純に 「関数」 と表記しています。
関数が返してくる値も「戻り値」や「返り値」など、
さまざまに呼ばれているようですが...
この記事では 「戻り値」 で統一しますね。
環境
慣れている Node.js の REPL で試してみました。
node のバージョンは 14.15.4 です。
N予備校の2021年度の教材の、
サーバーサイドプログラミング入門で最初に構築する環境です。
(オンライン教材なので、変わっている可能性も否定できませぬ...)
さっそく REPL を起動してみます。
node
とだけ入力して Node.js の REPL に入ります。
>
おなじみのプロンプトが表示されたので、きっと環境構築には成功しているはずです!
やったー!
もう、人生のピークですね! ( ※ 個人の感想です )
いざ実験
さっそく配列を使ってみます。
試しに りんご だけの配列を作って、表示してみます。
> const arr = ['りんご'];
> console.log(arr);
[ 'りんご' ]
中身が 'りんご'
だけの配列ができたので、
この配列に バナナ を追加したくなりました。誰でもそうですよね。
> arr[1] = 'バナナ';
> console.log(arr);
[ 'りんご', 'バナナ' ]
バナナが追加されて[ 'りんご', 'バナナ' ]
になりました。
const で定義していても追加できるようですね。
さらにみかんも追加して、ついでに fruits という変数に入れたいと思います。
配列の最後に要素を追加する push( ) という関数を使えば、1行で書けるのでは?
試してみましょう。
> const fruits = arr.push('みかん');
> console.log(arr);
[ 'りんご', 'バナナ', 'みかん' ]
arr にみかんが追加され、 [ 'りんご', 'バナナ', 'みかん' ]
になっているので、
fruits の中身も、きっと [ 'りんご', 'バナナ', 'みかん' ]
に違いありません。
表示してみます。
> console.log(fuits);
3
fruits の値は 3
でした。
何が起きたというのでしょうか?
数値や文字列のときは、
関数の目的がそのまま戻り値に反映されていたのに。
push( ) は配列に要素を追加する関数のはずなのに。
なんで変わっちゃったの!?
昔はもっと素直だったじゃん!!
そんな気分です。
てんびん座の私ですらそう思うのですから、
きっと皆さんのいきどおりは相当なものでしょう。
調べてみると push( ) 関数は、
その戻り値として要素の数を返しているようです。
どうやら、関数の「売り」が配列の操作だった場合、
戻り値は「別の何か」ということがほとんどのようです。
ここでは配列自身の変化を、
関数の 「副作用」 と呼ぶことにします。
先ほどの
> const fruits = arr.push('みかん');
で言えば...
fruits に入る値が 戻り値
arr の変化が 副作用
です。
ということで...
関数の戻り値と副作用を、この私がじきじきに調べてみました。
ありがたいですね。
配列の関数
いくつか代表的な関数を調べてみました。
戻り値が空欄なら、戻り値の無い関数です。
副作用が空欄なら、配列自体には変化の無い関数です。
どの関数も arr
の初期値は ['りんご', 'バナナ']
になっています。
上から順番に試したものではありません。
たとえば arr.pop( ) の場合でも、 arr の初期値は ['りんご', 'バナナ']
です。
関数 | 概要 | 試したコード | 戻り値 | 副作用(実行後の配列) |
---|---|---|---|---|
push | 末尾に要素を追加して 要素の数を返す |
arr.push( 'みかん' ); | 3 | [ 'りんご', 'バナナ', 'みかん' ] |
unshift | 先頭に要素を追加して 要素の数を返す |
arr.unshift( 'みかん' ); | 3 | [ 'みかん', 'りんご', 'バナナ' ] |
shift | 先頭の要素を取り出す | arr.shift( ); | 'りんご' | [ 'バナナ' ] |
pop | 末尾の要素を取り出す | arr.pop( ); | 'バナナ' | [ 'りんご' ] |
splice | ◯番めから△個分を置換 (追加や削除もできるよ) |
①arr.splice( 0, 1, 'いちご' ); ②arr.splice( 0, 1 ); |
① [ 'りんご' ] ② [ 'りんご' ] |
① [ 'いちご', 'バナナ' ] ② [ 'バナナ' ] |
slice | ◯番めから△番めの手前まで取得 単純な配列のコピーに便利 |
arr.slice( 0, 1 ); | [ 'りんご' ] | |
indexOf | 要素の添字を取得 | arr.indexOf( 'バナナ' ); | 1 | |
toString | 配列を文字列に変換 | arr.toString( ); | 'りんご,バナナ' | |
join | 文字列に変換する 区切り文字を指定可 |
arr.join(' / '); | 'りんご / バナナ' | |
concat | 配列をつなぐ | const array1 = ['a', 'b', 'c']; const array2 = ['d', 'e', 'f']; array1.concat( array2 ); |
[ 'a', 'b', 'c', 'd', 'e', 'f' ] |
|
reduce | 配列の内容を1つにまとめる 数学の Σ とか Π みたいな |
const num = [ 1, 2, 3, 4 ]; num.reduce( ( p, c ) => p + c ) |
10 |
|
reverse | 要素を反転する | const count = ['one', 'two', 'three']; count.reverse( ); |
[ 'three', 'two', 'one' ] |
[ 'three', 'two', 'one' ] |
sort | 配列のソート | const month = ['Jan', 'March', 'Dec']; month.sort( ); |
[ 'Dec', 'Jan', 'March' ] |
[ 'Dec', 'Jan', 'March' ] |
戻り値が配列になったり、ならなかったり、
slice( ) と splice( ) のややこしさといったら...
おまけ: 連想配列の関数
連想配列だとどうかな?と思って調べてみました。
myMap
の初期値は Map { 'foo' => 'bar' }
です。
関数 | 概要 | 試したコード | 戻り値 | 副作用(実行後のMap) |
---|---|---|---|---|
set | 追加 | myMap.set( 'hoge', 'fuga' ); | Map { 'foo' => 'bar', 'hoge' => 'fuga' } | Map { 'foo' => 'bar', 'hoge' => 'fuga' } |
get(key) | 参照 | myMap.get('foo'); | 'bar' | |
delete(key) | 要素の削除 | myMap.delete('foo'); | true | Map { } |
clear | 全部削除 | myMap.clear( ); | Map { } | |
has(key) | あるかどうか | myMap.has('foo'); | true |
配列の関数に比べると素直な印象でしたが、いかがでしょうか?
まとめ
ちょっと幅が窮屈な感じで、
見にくい表になってしまいました。
表の全体を画像にしてしまう事も考えたのですが、
コピペで試せた方が良いのかな? と思ったり...
ただメンドクサイだけだったり...
どなたか、表にまとめることの得意な方がいらっしゃいましたら、
アドバイスなどいただけると喜びます。
ということで、私が関数を使ったり調べたりするときには、
戻り値と副作用を意識するようになりました!というお話でした。