日々の積み上げブログ

プログラミングを習得したい

Gitでブランチを切るのを忘れて間違えてmasterにコミットしてしまったときの対処法

誤ってmasterブランチでコミットしてしまっても、そのコミットの変更内容を別のブランチに移すことで対処できます

① どこのコミットまで変更内容を移したいのか確認する

# masterブランチのコミット履歴を1行で表示させます
$ git log --oneline master
813c118 (HEAD -> master) Add:  # 移動したい変更3
a7ba267 Add: 〇〇〇〇 # 移動したい変更2
3dfd56a Add: 〇〇〇〇 # 移動したい変更1
7dd0b34 (origin/master) Merge pull request 
2a3701c (origin/development) 
ede71f6 Add:〇〇〇〇
8a05884 Add: 〇〇〇〇
a1f10f4 first-commit

上記より、7dd0b34から上の3つのコミットが誤ってコミットしてしまった部分だと分かります。

② 変更の移動先となるブランチがまだ作成されていない場合は、移動先となるブランチを作成します

# ブランチ名を作成し移動
$ git checkout -b ブランチ名

# ブランチ一覧表示と今いるブランチの確認
$ git branch

git reset --hardコマンドでmasterブランチから移動したい変更のコミットを削除します

今回は、3つのコミットを移動させたいので、masterブランチから最新の3つ分のコミット履歴を削除します。ただし、git reset --hardを使う場合は、プッシュ前のコミットに対してのみ、使うようにしましょう。

  • HEADは、今自分がいるブランチの先頭のことで、自分が作業している場所を示すポインタです。
# masterブランチにいることを確認する
$ git branch
* master
  posts

# masterブランチで、git reset --hardを使います。
# masterブランチの指定した時点まで「HEAD(今自分がいるブランチの先頭)・ステージングエリア・作業ディレクトリ」のすべてが巻き戻されます。(今回は3つ前まで)
$ git reset --hard HEAD~3
HEAD is now at 7dd0b34 Merge pull request #1 from name/development

# masterブランチのコミット履歴を1行で表示
$ git log --oneline master 
7dd0b34 (HEAD -> master, origin/master) Merge pull request 
2a3701c (origin/development) Add:〇〇〇〇
ede71f6 Add:〇〇〇〇
8a05884 Add: 〇〇〇〇
a1f10f4 first-commit

これで、今いるmasterブランチの先頭がリモートリポジトリの内容をプルした部分まで巻き戻りました。

④ 移動したいブランチで作業の続きを行います

masterブランチのコミットは削除しましたが、移動したブランチ内ではコミット履歴が残っていることが確認できました。

# masterからpostブランチへ移動
$ git checkout posts

# ブランチ確認
$ git branch
* master
  posts

# postsブランチのコミット履歴を表示
$ git log --oneline posts 
813c118 (HEAD -> posts) Add: 〇〇〇〇 # 移動した変更3
a7ba267 Add: 〇〇〇〇 # 移動した変更2
3dfd56a Add: 〇〇〇〇 # 移動した変更1
7dd0b34 (origin/master, master) Merge pull request 
2a3701c (origin/development) Add:〇〇〇〇
ede71f6 Add:〇〇〇〇
8a05884 Add: 〇〇〇〇
a1f10f4 first-commit

参考

[Git]誤ったブランチで実施した変更を正しいブランチに移動する | DevelopersIO

第6話 git reset 3種類をどこよりもわかりやすい図解で解説!【連載】マンガでわかるGit ~コマンド編~ - itstaffing エンジニアスタイル

ローカルで新規プロジェクトを立ち上げてから、GitHubのリモートリポジトリに結びつけるまでの一連の手順

1. ローカルでプロジェクトを立ち上げたら、まずローカルで空のcommitを実行します。

Gitは、初回のコミット後に新しくブランチを作成できるようになります。そのためまずは、空のコミットを実行してGitの機能を使えるようにしていく必要があります。

① 空のコミットを実行

GitHubへアップロードしたいプロジェクトディレクトリへ移動してから、Gitの初期化を行います。git init後、作業ディレクトリでgitが使えるようになります。

# Gitの初期化
$ git init

# 空のcommitを実行(--allow-emptyオプションを付けると空のコミットが実行できます。)
$ git commit --allow-empty -m "ファーストコミット名"

⇒ 実行後、新しいブランチ(master)が作成されています。

② リモートリポジトリの登録

git remote add origin リモートリポジトリの場所」で、現在のローカルリポジトリに指定したリモートリポジトリを登録します。

# 新しいリモートリポジトリをローカルと結びつける
$ git remote add origin https://github.comユーザー名/リポジトリ名
#=> 現在のローカルリポジトリに、「origin」という名前で、https://github……という場所《URL》をリモートリポジトリとして追加する

2. ローカルで実行した空のコミットをローカルリポジトリにプッシュしていきます。

①ローカルのmasterブランチからリモートのmasterブランチへPush

$ git push -u origin master

⇒ 実行後、作業に入る前に新しくブランチを切ってから作業をしていきます。

3. gitを使用したブランチ作成からpushまでの簡単な流れ

ここからは、いつも通りの作業になります。作業ごとにブランチを分けてプッシュ、マージ後プルをしていきます。

① 作業を開始前に新しくブランチを切ります。

# 今いるブランチを確認(-aオプションをつけることでリモートブランチも見れます)
$ git branch -a
または
$ git branch

# 新しくブランチを作成
$ git branch ブランチ名

# 作成したブランチに移動
$ git checkout 作成したブランチ名
または、
# ブランチ作成と移動を同時に行う
$ git checkout -b ブランチ名

ファイルを編集後、コミットを実施する

# ファイルの編集状態確認(変更差分のあるファイルのみが出力されます)
$ git status

# 変更差分のあるファイル全てをコミット対象に追加(ファイルを個別に追加することもできます。)
$ git add .

# コミット対象に追加したファイルをコミットメッセージ付きでコミット
$ git commit -m "コミットコメント"

# コミット履歴を表示(--all --oneline --graphオプションを付けると見やすくなる)
$ git log --all --oneline --graph

③ ローカルブランチでコミットしたファイルをリモートのブランチにプッシュする

-uオプションは、ローカル側の現在いるブランチに紐づくリモート側のブランチを作成&登録します。–set-upstreamと同じ意味です。

オプションを付けて実行すると、今後リモートのブランチ名を指定せずにgit pushコマンドのみでgit push origin ブランチ名と同じ実行ができるようになります。

# リモートリポジトリに指定した名前でブランチを作成し、そのブランチにプッシュを実施する
$ git push -u origin ブランチ名
または、(HEADを付けるとブランチ名を書く必要がなくなります)
$ git push origin HEAD

④ リモートリポジトリ側でpushしたコミットをmasterブランチにマージします。

特に問題がなければ、masterブランチにマージを行いましょう。 これでリモートのmasterブランチに最新のファイルが読み込まれました。

マージ後に、リモートの作業ブランチを削除するようにしましょう。

⑤ ローカル側のmasterブランチにリモート側のmasterブランチの内容をpullします。

# ローカルで作業ブランチからmasterブランチに移動
$ git checkout master

# リモートのmasterブランチからマージしたファイルを取得する
$ git pull origin master

参考

【GitHub】GitHubで新規リポジトリを作成後、最初のコミットは空で行いましょう。 - 文系Webエンジニアの技術メモとたまに旅のメモ

gitを使用したブランチ作成からpushまでの簡単な流れ - Qiita

gitを使用したブランチ作成からpushまでの簡単な流れ - Qiita

【 git remote 】コマンド(基礎編)――リモートリポジトリを追加、削除する

マイグレーションファイル作成時にcannot load such file -- net/smtp (LoadError)エラー

実現したいこと

Migrationファイルを作成しようと、以下のコマンドを実行したところエラーが発生し、実行できませんでした。

$ rails g migration マイグレーションファイル名

エラーメッセージ

cannot load such file -- net/smtp (LoadError)

エラー原因

Ruby3.1で標準ライブラリからいくつかのライブラリが除外されていたことにより、マイグレーションファイル作成時に使用しようとしたnet-smtpライブラリが見つかりませんというエラーでした。

以下のライブラリが新たに bundled gems になりました。Bundler から利用する場合は Gemfile に明示的に指定してください。

  • net-ftp 0.1.3
  • net-imap 0.2.2
  • net-pop 0.1.1
  • net-smtp 0.3.1
  • matrix 0.4.2
  • prime 0.1.2
  • debug 1.4.0

https://www.ruby-lang.org/ja/news/2021/12/25/ruby-3-1-0-released/

Ruby 3.1では、上記のgemが標準ライブラリから除外されて、必要な際は個別にGemfileに記載してインストールする必要があります。

解決法

net-smtpをGemfileに記載後、bundle installコマンドでインストールをしました。

gem 'net-smtp'

その後、$ rails g migration マイグレーションファイル名を実行し、マイグレーションファイルを再度作成しようとしたのですが、、、今度は、違うエラーが出てしまいました。

2つ目のエラーメッセージ

Cannot load database configuration:
Unknown alias: default (Psych::BadAlias)

エラー原因2つ目

psychというYAML解釈用のGemの4系と3系で解釈が変わっているためです。

Rubyには標準でpsychというYAML解釈用のGemがあります。しかし、新しいバージョンである4系からは、YAMLの解釈方法が変わったため、今まで読み込むことができていたYAMLファイルを読み込めなくなってしまったのです。

2つ目解決法

gemは、バージョンを記載しないと新しいバージョンが優先して利用されるため、3系を使いたい時はgemfileにバージョンを明示してあげます。

gem 'psych', '~> 3.1'

その後、bundle installを実行しました。3系であれば以前と同じ解釈法が使われるため上手く読み込むことが出来ました。マイグレーションファイルも無事作成出来ました!!やったー!!

$ rails g migration マイグレーションファイル名
=> Running via Spring preloader in process 77124
      invoke  active_record
      create    db/migrate/マイグレーションファイル名.rb

参考にしたURL

Ruby3.1でherokuにデプロイしたらクラッシュした

Ruby 3.1.0 リリース

psych::badalias: unknown alias: defaultで Railsコマンドが使えないを解決

2022/05/11(水)~05/17(火) JSPrimer(ループと反復処理)

同じ処理を繰り返したい時は、ループやイテレータなどを用いて、反復処理として同じ処理を繰り返し実行できます。

while文

while文は、条件式がtruthy(trueに変換)の時に処理を繰り返し、falsy(falseに変換)である場合は、何も実行せずwhile文を終了します。
条件式が最初からfalsyな値である場合には、while文の処理は1度も実行されません。

while (条件式) {
    実行する文; // 条件式がtruthyの場合に繰り返し処理されるコードを記述
}
  1. 条件式 の評価結果がtrueなら次のステップへ、falseなら終了
  2. 実行する文を実行
  3. ステップ1へ戻る

while文 使用例

let x = 0;
console.log(`ループ開始前のxの値: ${x}`);
while (x < 10) { // xが10になるまで繰り返し処理を実行(xの初期値が0なので、10回繰り返します)
    console.log(x);
    x += 1;  // 繰り返し処理の最後にxの値を1増やす 
}
console.log(`ループ終了後のxの値: ${x}`);

/* => ループ開始前のxの値: 0
0
1
2
3
4
5
6
7
8
9
ループ終了後のxの値: 10  */
  • xの値が10未満であるなら、コンソールへ繰り返しログが出力されます。
  • 繰り返し処理の最後でxの値を1ずつ増やし、xの値が10になった時に条件式がfalseと評価されるためwhile文が終了します。
  • 繰り返し処理の中で条件式がfalseとなるような処理を書かないと無限ループになってしまうため、それを防ぐためにxの値を1ずつ増やす処理が必要です。

無限ループ

条件式の評価結果が常にtrueと評価されると無限ループが起こります。無限ループが起こると、JavaScriptの繰り返し処理が無限に続いてしまいブラウザの画面がフリーズしてしまいます。
無限ループが発生した場合は、ブラウザのタブを閉じて、コードを修正した後、再度ブラウザを開くようにしましょう。

let i = 1;
// 条件式が常にtrueになるため、無限ループする
while (i > 0) {
    console.log(`${i}回目のループ`);
    i += 1;
}

do-while文

do-while文はwhile文と似ていますが、まず最初にdoブロック内の処理を実行し、その後while文で条件式の判定を行います。
while文と異なる部分は条件式の判定に関係なく、必ず1度はブロック内の処理が実行されるということです。

do {
    実行する文;  // 1番最初に実行される処理かつ条件式がtrueと評価された時に実行する処理
} while (条件式);
  1. 実行する文を実行
  2. 条件式 の評価結果がtrueなら次のステップへ、falseなら終了
  3. ステップ1へ戻る

do-while文 使用例

const x = 1000;
do {
    console.log(x); // => 1000
} while (x < 10);
  • 最初から条件式を満たさない場合でも、 初回の繰り返し処理が実行され、コンソールへ1000と出力されます。
  • 条件式x < 10はfalseと評価されるため、判定の部分で繰り返し処理を終了します。

for文

for (初期化式; 条件式; 増分式) {
    実行する文;
}
  1. 初期化式でループで使用する変数の宣言を行います。(繰り返し処理が始まる1番初めのみ実行されます。)
  2. ループ継続の条件式の評価結果がtrueである限りforブロック内の処理は繰り返し実行されます。falseと評価された瞬間に繰り返し処理は終了します。
  3. 増分式で繰り返し処理実行後にループに使う変数の更新処理が行われます。
  4. ステップ2へ戻る

for文 使用例

// 配列の要素の合計値を返す関数
function sum(numbers) {
    let total = 0;
    // 配列の要素数分だけ繰り返し処理を実行
    for (let i = 0; i < numbers.length; i++) {
        total += numbers[i];
    }
    return total;  // 合計値を戻り値として返す
}

console.log(sum([1, 2, 3, 4, 5])); // => 15
  • numbers配列に含まれている要素を先頭から順番に変数totalへ加算することで合計値を計算しています。

配列のforEachメソッド(Arrayオブジェクトのメソッド)

JavaScriptの配列であるArrayオブジェクトには、反復処理のためのメソッドが備わっています。その一つがforEachメソッドです。

forEachメソッドは、配列の各要素に対する繰り返し処理をコールバック関数を利用して記述できるメソッドです。つまり、関数を引数として取ることができる高階関数であるということです。
具体的には、引数として与えられた関数を、配列の各要素に対して一度ずつ実行します。

配列.forEach( function(value, index, array){ // 引数にコールバック関数
  /*配列の各要素に対して実行される処理*/
});
  • コールバック関数の引数
    • valueには、配列の要素の値が1つずつ渡されてきます。
    • indexには、配列のインデックス番号が0から順番に渡されてきます。(省略可)
    • arrayには、配列自体が渡されます。(省略可)
  • 繰り返し処理は、配列の要素数分だけ実行されます。

forEach 使用例

// 配列の要素の合計値を返す関数
function sum(numbers) {
    let total = 0;
    numbers.forEach(num => {
        total += num;  // 配列の個々の要素に対して呼び出される処理(コールバック関数の処理)
    });
    return total;
}

sum([1, 2, 3, 4, 5]); // => 15
  • コールバック関数の引数numに配列の各要素が順番に入ってきます。
  • 繰り返し処理部分をfor文ではなくforEachメソッドを使って記述しています。
  • forEachメソッドは必ず配列のすべての要素を反復処理します。

break文

break文は処理中の文から抜けて次の文へ移行する制御文です。 while、do-while、forの中で使い、処理中のループを終了して次の処理へ移行します。

while (true) {
    処理A;
    break; // 繰り返し処理をここまでで終了するため、処理Bは実行されない
    処理B;
}
処理C; // 繰り返し処理の処理Aが実行された場合、その次は処理Cが実行される

break文 使用例①

// 引数の`num`が偶数ならtrueを返す関数
function isEven(num) {
    return num % 2 === 0;
}
// 引数の`numbers`に偶数が含まれているならtrueを返す
function isEvenIncluded(numbers) {
    // 偶数があるかどうかをtrueかfalseで表す変数(初期値false)
    let isEvenIncluded = false;
    for (let i = 0; i < numbers.length; i++) { // 要素数分判定を繰り返す
        const num = numbers[i]; // 1つずつ取得した要素の値を変数numへ代入
        if (isEven(num)) {  // 要素の値が偶数かどうかを判定し、
            isEvenIncluded = true;  // 偶数であった場合に変数isEvenIncludedにtrueを代入
            break;  // 配列内の要素に1つでも偶数があるかがわかればいいため、最初の偶数を見つけた時点で、for文を終了する
        }
    }
    return isEvenIncluded;
}
const array = [1, 5, 10, 15, 20];
console.log(isEvenIncluded(array)); // => true
  • 1つでも偶数があるかがわかればいいため、配列内から最初の偶数を見つけたらfor文での反復処理をbreak文で終了します。
  • break文の記述は、現在の関数を終了させることができるreturnを使って記述することもできます。
    • break; => return true;
    • return isEvenIncluded; => return false

上記のコードをfor文を使わず配列のメソッドを使って簡潔に書いたバージョン

const numbers = [1, 5, 10, 15, 20];

// 配列の要素に1つでも偶数を含んでいるかを判定する関数
const includeEvenNumber2 = (array) => { 
    return array.some(num => num % 2 === 0);
};

// 関数呼び出し
console.log(includeEvenNumber2(numbers)); // => true
  • someメソッドは、配列の少なくとも一つの要素が条件に一致すればtrueを返す配列メソッドです。MDN-Array.prototype.some()

配列のsomeメソッド(Arrayオブジェクトのメソッド)

someメソッドは、配列の少なくとも一つの要素が条件に一致すればtrueを返す配列メソッドです。
someメソッドでは、配列の各要素をテストする処理をコールバック関数として受け取ります。条件となるコールバック関数が、一度でもtrueを返した時点で反復処理を終了し、最後にtrueを返します。

continue文

continue文は現在の反復処理を終了して、次の反復処理を行います。 continue文は、while、do-while、forの繰り返し処理の中で使うことができます。

画像参照

違い
break 繰り返し処理全体を終了して、次の処理へ
continue 現在の繰り返し処理を終了して、次の繰り返し処理を実行

continue文 使用例

// 配列の各要素が偶数ならtrueを返す関数
function isEven(num) {
    return num % 2 === 0;
}
// 配列の要素の内、偶数のものだけを取得する関数
function filterEven(numbers) {
    const results = []; // 偶数の要素を入れるための空の配列を作成
    for (let i = 0; i < numbers.length; i++) { // 配列の要素数分繰り返し処理をする
        const num = numbers[i];  // 順番に取得した要素の値を変数numに代入
        // 偶数ではないなら、次のループへ
        if (!isEven(num)) {
            continue; // 変数numに代入した要素が偶数でないと分かった時点で現在のループ処理を終了し、次の要素のループ処理に移行する
        }
        // 偶数を`results`に追加
        results.push(num); // 要素の値が偶数だと判定できたので、新しい配列resultsに要素numを追加する
    }
    return results;
}
const array = [1, 5, 10, 15, 20];
// 関数呼び出して実行
console.log(filterEven(array)); // => [10, 20]
  • 配列の中から偶数を集め、新しい配列を作り返しています。
  • 配列の要素が偶数ではない場合、処理途中のfor文をcontinue文でスキップしています。

配列のfilterメソッド

filterメソッドは、配列から特定の値だけを集めた新しい配列を作ることができます。
filterメソッドは、配列の各要素を順番にコールバック関数の条件と照らし合わせ、条件に当てはまる要素のみを保持する新しい配列を作成します。

let newArray = arr.filter(callback(element[, index, [array]])[, thisArg])
  • callback: 配列の各要素に対して実行するテスト関数です。この関数が true を返した要素は残され、false を返した要素は取り除かれます。
  • element: 配列内で処理中の現在の要素です。順番に1つずつ渡されます。
  • index(省略可): 配列内で処理中の現在の要素のインデックス番号です。順番に渡されます。
  • array(省略可): 配列自体です。

filterメソッドを使った使用例 偶数のみを保持する配列を作成します。

const array = [1, 2, 3, 4, 5, 6, 7, 8];

const filteredArray = array.filter((currentValue, index, array) => {
    // 偶数ならtrue、そうでないならfalseを返す
    // trueを返した要素のみの配列を新しく作成
    return currentValue % 2 === 0;
});

console.log(filteredArray); // => [ 2, 4, 6, 8 ]

for...in文

for...in文はオブジェクトのプロパティに対して、反復処理を行います。オブジェクトにfor...in文を使うと、オブジェクトのプロパティの数分だけ繰り返し処理が行われます。

for (const key in obj) {
    オブジェクトの各プロパティに対する繰り返し処理;
}
  • key: キーを使って、オブジェクトのプロパティを1つずつ取得します。
  • obj: オブジェクトや配列を設定できます。

for...in文の使用例

// 反復処理に使うオブジェクト
const obj = {
    "a": 1,
    "b": 2,
    "c": 3
};
// 注記: ループのたびに毎回新しいブロックに変数keyが定義されるため、再定義エラーが発生しない
for (const key in obj) { // objオブジェクトからkey名で各プロパティを取得し、変数keyに代入
    // オブジェクトの各プロパティに対する繰り返し処理
    const value = obj[key];
    console.log(`key:${key}, value:${value}`);
}
//=> "key:a, value:1"
//=> "key:b, value:2"
//=> "key:c, value:3"
  • objのプロパティ名をkey変数に代入してプロパティの値の分だけ反復処理を行います。
  • objには、3つのプロパティ名があるため繰り返しが3回行われます。
  • constで定義している変数keyはループのたびに毎回新しいブロックを作成し新たに定義されているため、再定義エラーになることはありません。

for...in文の問題点

JavaScriptでは、オブジェクトは何らかのオブジェクトを継承しています。 for...in文は、対象となるオブジェクトのプロパティを列挙する場合に、親オブジェクトまで列挙可能なものがあるかを探索して列挙します。 そのため、オブジェクト自身が持っていないプロパティも列挙されてしまい、意図しない結果になる場合があります。

安全にオブジェクトのプロパティを列挙するには、Object.keysメソッド、Object.valuesメソッド、Object.entriesメソッドなどが利用できます。

また、配列に対してもfor...in文を使うべきではありません。なぜなら、for...in文が列挙する配列オブジェクトのプロパティ名は、要素の値ではなく、要素のインデックスを文字列化した値だからです。配列の要素をループ処理させたいときは、for...of文for文または、forEachメソッドを使うようにしましょう。

  • Object.keys(): 引数で渡されたオブジェクト自身が持つ列挙可能なプロパティ名の配列を返すメソッドです。

  • Object.values(): 引数で渡されたオブジェクトが持つ列挙可能なプロパティの値配列にして返します。

  • Object.entries(): : 引数で渡されたオブジェクトが持つ列挙可能なプロパティ名とプロパティ値のペア[key, value]の形式の配列として返します。

for...in文の使用例をfor...inを使わないで記述した例

const obj = {
    "a": 1,
    "b": 2,
    "c": 3
};
// Object.keysメソッドとforEachメソッドの組み合わせバージョン
Object.keys(obj).forEach(key => {
    const value = obj[key];
    console.log(`key:${key}, value:${value}`);
});
//=> "key:a, value:1"
//=> "key:b, value:2"
//=> "key:c, value:3"


// for...ofメソッドとObject .entriesメソッドの組み合わせバージョン
for(const [key,  value] of Object.entries(obj)){ // [key,  value]の部分は分割代入を行う
    console.log(`key:${key}, value:${value}`);
};
//=> "key:a, value:1"
//=> "key:b, value:2"
//=> "key:c, value:3"
  • Object.keysメソッドforEachメソッドの組み合わせバージョン
    • Object.keysメソッドを使い、objオブジェクトのプロパティ名配列を取得します。
    • forEachメソッドで取得したプロパティ名を1つずつ順番に使い、繰り返し処理を行います。
  • for...ofメソッドObject .entriesメソッドの組み合わせバージョン
    • Object.entriesメソッドでobjのプロパティ名と値のペアで取得した配列をそれぞれkeyとvalue変数に分割代入します。
    • keyにはプロパティ名、valueにはプロパティの値が入ります。

[ES2015] for...of文

for...of文は、反復可能オブジェクト(iterableオブジェクト)に対して繰り返し処理を実行できます。代表的な反復可能オブジェクトとは、配列(Array)やMap、Set、Stringオブジェクトなどです。

for ( const value of iterable ){
  // 反復可能オブジェクトの各要素に対する繰り返し処理
}
  • value : 反復処理の各回において、異なるプロパティの値が value に割り当てられます。
  • iterable : 反復可能オブジェクト。(配列、Map、Set、String、argumentsなど)

for...of文の使用例

const array = [1, 2, 3];
for (const value of array) {  // 配列の各要素を繰り返し処理
    console.log(value);
}
//=> 1
//=> 2
//=> 3
  • 配列arrayの要素を1つずつ取得して、value変数に代入したものを繰り返し処理しています。
  • 配列arrayの要素が3つあるので3回繰り返しが実行されます。

反復可能オブジェクト(iterableオブジェクト)とは?

Symbol.iteratorという特別な名前のメソッドを実装したオブジェクトをiterableと呼びます。簡単にまとめると、反復処理時の動作が定義されたオブジェクトがiterableオブジェクトです。また、iterableオブジェクトは反復処理時に次の返す値を定義しています。

for...in文とfor...of文の使い分け

名前 繰り返し取得できる値 繰り返し処理するべきもの  説明
for...in文 key(プロパティ名、インデックス番号) オブジェクトのループ オブジェクトのすべての列挙可能なプロパティに対して、順序不定で繰り返し処理を実行
for...of文 value(keyに対応する値) 反復可能オブジェクトのループ(配列、Map、Set、String、argumentsなど) 反復可能なオブジェクトが定義した順序で値を反復処理

reduce メソッド

reduceメソッドは2つずつ要素を取り出し(左から右へ)、その値をコールバック関数に適用し、 次の値として1つの値を返します。 最終的な、reduceメソッドの返り値は、コールバック関数が最後にreturnした値となります。

const result = array.reduce((前回の値, 現在の値) => {
    return 次の値;
}, 初期値);

以下の例では、配列のすべての要素の和を返すsumWithInitial関数を定義しています。

const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = (array) => array.reduce(
  (previousValue, currentValue) => previousValue + currentValue,
  initialValue
);

console.log(sumWithInitial(array1));  //=> 10

参考

JSPrimer-ループと反復処理

独習JavaScript

MDN-Array.prototype.forEach()

MDN-Array.prototype.some()

2022/05/09(月) JSprimer(条件分岐)

条件分岐

条件分岐を使うと、特定の条件を満たすかどうかで行う処理の流れを分岐できます。
条件分岐を行うには、if文またはswitch文を使います。

if文

if文は、丸括弧内()条件式trueと評価されるtruthyな値の場合、それに続くブロック{}内の処理が実行されます。ただし、条件式falseと評価されるfalsyな値の場合は、ブロック{}内の処理は実行されず、if文の次の行に処理が進みます。

if (条件式) {
    実行する文; // 条件式がtruthyの場合実行する文(falsyの場合実行されない)
}
  • 実行する文が1行のみの場合は、{ }のブロックを省略できますが、どこまでがif文かを明確にするためにも常にブロックで囲むようにしましょう。
const x = 42;
// 条件式x > 10に当てはまるかどうかで処理を分岐している
if (x > 10) { // x > 10の条件式がtrueと評価されるtruthyな値であるため、以下の処理が実行されます。
    console.log("xは10より大きな値です");
}
const truthy = "truthyな値です。";

if ( truthy ) {
    console.log("条件式に渡された値は、truthyです。");
} else {
    console.log("条件式に渡された値は、falsyです。");
}
  • if文の条件式には、比較演算子などが使われ、その比較結果によって処理を分岐したい時などに使います。
  • if文の条件式に文字列や数値などの真偽値以外の値が単純に渡された場合には、その値を暗黙的に真偽値へ変換してから、条件式として判定します。(truthyfalsyかを判定)
    • truthyならtrueと変換されます。
    • falsyならfalseと変換されます。以下は、falseと変換されるfalsyな値7つ。
      • false
      • undefined
      • null
      • 0
      • 0n
      • NaN
      • ""(空文字列)
// falsyな値を条件式に指定した場合は、falseと変換されるためif文のブロック文は実行されません。
if (false) {
    // この行は実行されません
}
if ("") {
    // この行は実行されません
}
if (0) {
    // この行は実行されません
}
if (undefined) {
    // この行は実行されません
}
if (null) {
    // この行は実行されません
}

else if文

if文に続けてelse if文をつなげて記述することで、複数の条件式を使った条件分岐を作成できます。

if(条件式A) {
    console.log('ifブロック'); // 条件式Aがtrueと評価された場合に実行されるブロック
}else if(条件式B){
    console.log('else ifブロック');  // 条件式Aがfalseと評価されたかつ、条件式Bがtrueと評価された場合に実行されるブロック
}

else文

if文の条件式がfalseと評価され、条件に一致しなかった場合の処理は、else文を使うことで書くことができます。

const num = 1;
if (num > 10) { // 条件式で、変数numの数値が10より大きいかを判定
    console.log(`numは10より大きいです: ${num}`); // 条件式がtrueと評価された場合、実行されるブロック
} else {
    console.log(`numは10以下です: ${num}`); // 条件式がfalseと評価された場合に実行されるブロック
}

switch文

switch文は、条件式の評価結果が指定した値である場合に実行する処理を並べて記述することができます。if文と同様に評価結果の値によって、処理を分岐することができます。

const version = "ES6";
switch (version) {
    case "ES5": // if (version === "ES5")が行われている
        console.log("ECMAScript 5");
        break;
    case "ES6":  // versionの評価結果は"ES6"となるため、case "ES6":に続く文が実行
        console.log("ECMAScript 2015");
        break;
    case "ES7":
        console.log("ECMAScript 2016");
        break;
    default:
        console.log("しらないバージョンです");
        break;
}
// "ECMAScript 2015" と出力される

switch文をif文で記述したバージョン

const version = "ES6";
if (version === "ES5") {
    console.log("ECMAScript 5");
} else if (version === "ES6") {
    console.log("ECMAScript 2015");
} else if (version === "ES7") {
    console.log("ECMAScript 2016");
} else {
    console.log("しらないバージョンです");
}
// "ECMAScript 2015" と出力される
  • 一致する条件式の値がcase節のどれにも一致しない場合は、default節が実行されます。
  • 条件式の評価結果とcase節の値は、厳密等価演算子(===)を使って値の比較を行い、値が一致するか調べています。
  • 厳密等価演算子(===)を使っているため、値の型の一致まで求められます。
  • switch文のcase節では、最後にbreakというキーワードを使うことが一般的です。break文は、case節の1つに当てはまる処理が実行できた場合にswitch文のブロックから抜けて、switch文の次の処理に移るためのものです。

break文

switch文のcase節では基本的にbreak;を使って当てはまる処理実行後にswitch文を抜けるようにします。 このbreak;を省略した場合、後ろに続くcase節が条件式の結果に関係なく実行されます(trueでもfalseでも実行)。

const version = "ES6";
switch (version) { 
    case "ES5":  // (version === "ES5")はfalseと評価されるため、以下の処理は実行されない
        console.log("ECMAScript 5");
    case "ES6": // 一致するケース
        console.log("ECMAScript 2015");
        // break; ここにbreak文を記述すれば、以降の処理は実行されずに済む
    case "ES7": // breakされないため条件無視して実行
        console.log("ECMAScript 2016");
    default:    // breakされないため条件無視して実行
        console.log("しらないバージョンです");
}
/*
 "ECMAScript 2015"
 "ECMAScript 2016"
 "しらないバージョンです"
 と出力される
 */
  • 一致するケース実行後にbreak文を記述していないと以降のcase文が条件を無視して実行されてしまうので注意が必要です。
  • case節とbreak文が多用されているswitch文は、if、elseを使って書き換える方法も検討しましょう。

まとめ

  • if文、else if文、else文で条件分岐した処理を扱える
  • 条件式に指定した値は真偽値へと変換してから判定される
  • 真偽値に変換するとfalseとなる値をfalsyと呼ぶ
  • switch文とcase節、default節を組み合わせて条件分岐した処理を扱える
  • case節でbreak文しない場合は引き続きcase節が実行される

参考

JSPrimer-条件分岐

2022/05/04(水)~ 05/05(木) JSprimer(文と式)

2022/05/04(水)~ 05/05(木) JSprimer(文と式)

JavaScriptは、(Statement)と(Expression)から構成されています。

(Expression)とは、値を生成し、変数に代入できるもののことを言います。式の特徴として、式を評価すると結果の値(評価値)を得ることができます。
つまり、評価した結果を変数に代入できるものはです。

// 1という式の評価値を表示
console.log(1); // => 1
// 1 + 1という式の評価値を表示
console.log(1 + 1); // => 2
// 式の評価値を変数に代入
const total = 1 + 1;
// 変数に代入した式の評価結果を出力
console.log(total); // => 2
// 関数式の評価値(関数オブジェクト)を変数に代入
const fn = function() {
    return 1;
};
// fn() という式の評価値を表示
console.log(fn()); // => 1

(Statement)とは、ブラウザ(PC)に対してプログラムのソースコードを使って命令するときのひとまとまりのことです。
JavaScriptでは、セミコロン(;)を使って文を区切ります。つまり、セミコロンまでが1つの文です。

ソースコードとして書かれたを上から処理していくことで、プログラムが実行されます。

// セミコロンで文を区切る
// セミコロンまでが1つの文
処理する文;
処理する文;
処理する文;
const isTrue = true;
// isTrueという式がif文(文)の中に出てくる
// 式を文の処理の一部として埋め込むことができます。
if (isTrue) {
}

文と式の違い

文はそれ単独で完結する言語要素です。式はそれ単独では基本的に完結せず、文または式の一部として使用される言語要素です。また、式の最大の特徴として、値を返すという点が挙げられます(文は値を返しません)。 例えば、定数式はその値そのものを返します。条件式は真偽値(True/False)を返す式です。 式(Expression)と文(Statement) - よねKENのプログラミング研究

  • 式は、単独では成り立たず、文の処理の一部として埋め込むことができる言語要素です。
  • 文は、単独で成り立つもので、何らかの意味のある処理を行う1つの実行単位です。
  • 式は、値を返すが、文は値を返しません。
  • 式は文の一部になれますが、if文やfor文などの文は、式になることができません。(文は、変数に代入することができません。)
// 構文として間違っているため、SyntaxErrorが発生する
var forIsNotExpression = if (true) { /* ifは文であるため式にはなれない */ }

式文

(Statement)は(Expression)になることはできませんが、(Expression)は文の一部として埋め込むことができるため、(Statement)になることができます。
文の一部として埋め込まれた式のことを式文と呼びます。基本的に文が書ける場所には式を書けます。
式文(Expression statement)は文の一種であるため、セミコロンで文を区切ります。

// 式文であるためセミコロンをつけている
式;

ブロック文

文を{}で囲んだ部分をブロックと言います。 ブロックには、複数の文を書くことができます。
文の末尾にはセミコロンが必須ですが、例外でブロックで終わる文の末尾には、セミコロンが不要です。

// ブロックで終わらない文なので、セミコロンが必要
if (true) console.log(true);

// ブロックで終わる文なので、セミコロンが不要
if (true) {             // if文の処理内容に複数の文を書いている
    console.log(true);  // ブロック内には、複数の文を書ける
    console.log(false);
}  // ブロック文は、末尾にセミコロン不要(例外)
  • ブロック文は、if文やfor文など他の構文と組み合わせて書くことがほとんどです。

function宣言(文)とfunction式

関数を定義するには、functionキーワードから文を開始する関数宣言と、変数へ関数式を代入する方法があります。
関数宣言(文)と関数式は、どちらもfunctionというキーワードを利用しています。

// ①learn関数を宣言する関数宣言文
function learn(仮引数) {
    // 関数の処理
}
// ②関数式(匿名関数)をread変数へ代入
const read = function(仮引数) {
    // 関数の処理
};

// ②の書き換えバージョン
function fn() {}
// fn(式)の評価値を代入する変数宣言の文
const read = fn;

関数宣言(文)と関数式のセミコロン有無の違い

  • ①関数宣言(文)は、ブロックで終わる文であるため、セミコロンが不要となっています。
  • ②の関数式を変数へ代入したものは、変数を宣言する文の一部として匿名関数という式を埋め込んでいるだけなので、末尾にセミコロンが必要になります。

まとめ

  • JavaScriptは文(Statement)と式(Expression)から構成される
  • 文は式になれない
  • 式は文になれる(式)
  • 文の末尾にはセミコロンをつける
  • ブロックで終わる文は例外的にセミコロンをつけなくてよい

参考

JSPrimer-文と式

式(Expression)と文(Statement) - よねKENのプログラミング研究

プログラミングにおける式と文とは

2022/04/30(土)~05/03(火) JSprimer(関数と宣言)

関数とは?

関数とは、ある一連の手続き(文の集まり)を1つの処理としてまとめる機能です。
一度定義した関数を呼び出すことで同じ処理を何回でも実行できます。つまり、関数を使いまわすことが出来るようになります。

関数宣言

functionからはじまる文は関数宣言と呼び、関数を定義するために使われます。

// 関数宣言
function 関数名(仮引数1, 仮引数2) {
    // 関数が呼び出されたときの処理をブロック内に記述する
    return 関数の返り値;
}
// 関数呼び出し
const 関数の結果 = 関数名(引数1, 引数2); // 関数の結果を変数で受け取る
console.log(関数の結果); // => 関数の返り値

関数の構成要素

関数は次の4つの要素で構成されています。

  1. 関数名 - 関数の名前。関数を呼び出す際に使われる名前です。利用できる名前は変数名と同じです。.

  2. 仮引数 - 関数の呼び出し時に渡される実引数を受け取るための変数です。カンマで区切ると、複数の仮引数を記述できます。

  3. 関数の中身(実際の処理) - {}の間に関数の処理を書きます。

  4. 関数の返り値 - 関数の処理内でreturn文を記述すると、関数の実行結果を戻り値として呼び出し元に返すことが出来ます。

関数の特徴

  • 宣言した関数は、関数名()のように関数名にカッコをつけることで呼び出せます。
  • 関数を引数と共に呼ぶ際は、関数名(引数1, 引数2)とし、引数が複数ある場合は,(カンマ)で区切ります。
  • 関数の処理としてreturn文が実行されると、関数内のそれ以降の処理は行われません。
  • 関数が値を返す必要がない場合は、戻り値を省略できますが、return文そのものを省略することも出来ます。どちらの場合も未定義の値undefinedが返ります。
function fn() {
    // 何も返り値を指定してない場合は`undefined`を返す
    // return文を省略した場合にもundefinedが返る
    return;
    // すでにreturnされているため、これ以降の行は実行されません
}
console.log(fn()); // => undefined

関数の引数

JavaScriptでは、関数に定義した仮引数の個数と実際に呼び出したときの実引数の個数が違っても、関数を呼び出せます。

仮引数に比べて、呼び出し時の実引数の数が少ないとき

定義した関数の仮引数よりも呼び出し時の実引数が少ない場合、余った仮引数にはundefinedという値が代入されます。

// 実引数を1つ受けとって、実引数の値を返すecho関数
function echo(x) {
    return x;
}

console.log(echo(1)); // => 1

// 実引数を1つも渡さなかった場合、仮引数xにはundefindが代入される
console.log(echo()); // => undefined
// 実引数を2つ受け取り、実引数を要素とする配列を作成して返すargumentsToArray関数
function argumentsToArray(x, y) {
    return [x, y];
}

console.log(argumentsToArray(1, 2)); // => [1, 2]
// 実引数を1つしか渡さなかった場合、仮引数のxには1、yにはundefinedが代入される
console.log(argumentsToArray(1)); // => [1, undefined]

呼び出し時の実引数の数が少ないときの対応策:[ES2015] デフォルト引数

デフォルト引数デフォルトパラメータ)は、仮引数に対応する実引数が渡されていない場合に、デフォルトで代入される値を指定できます。 仮引数に対して仮引数 = デフォルト値という構文で、仮引数ごとにデフォルト値を指定できます。

// デフォルト値を設定した時の関数宣言
function 関数名(仮引数1 = デフォルト値1, 仮引数2 = デフォルト値2) {
  // 関数の処理
}
  • 実引数が渡されなかった場合にデフォルト値が仮引数に代入されます。
  • ES2015でデフォルト引数が導入されるまでは、OR演算子||)を使ったデフォルト値の指定がよく利用されていました。しかし、OR演算子||)を使ったデフォルト値の指定にはfalsyな値を渡すとデフォルト値が入ってしまうという問題点があります。
  • falsyな値とは、真偽値へと変換するとfalseとなる次のような値のことです。
    • false
    • undefined
    • null
    • 0
    • 0n
    • NaN
    • ""(空文字列)
// OR演算子(||)を使ったデフォルト値指定の問題点

function addPrefix(text, prefix) {
    // 仮引数prefixにデフォルト値をor演算子で設定する
    const pre = prefix || "デフォルト:";
    return pre + text;
}

// 2つ目の実引数を渡さなかった場合以外に、falsyな値(空文字や0など)を渡した場合でもデフォルト値が代入されてしまう
console.log(addPrefix("こんにちは")); // => "デフォルト:こんにちは"
console.log(addPrefix("こんにちは", "")); // => "デフォルト:こんにちは"
console.log(addPrefix("こんにちは", 0)); // => "デフォルト:こんにちは"

// 実引数を2つとも渡した場合は、デフォルト値は適用されず渡した実引数が代入される
console.log(addPrefix("こんにちは", "カスタム:")); // => "カスタム:こんにちは"
  • 上記から分かるように、2つ目の実引数を渡さなかった場合以外に、falsyな値(空文字や0など)を渡した場合でもデフォルト値が代入されてしまいます。 これが本当に0や空文字を渡したい場合だとデフォルト値が入るのは問題です。 このようにfalsyな値を実引数として渡した場合にデフォルト値が代入される事が意図した挙動なのかどうかがわかりにくくバグに繋がりやすいです。
  • ES2020から導入されたNullish coalescing演算子(??)を利用することでも、 OR演算子||)の問題を避けつつデフォルト値を指定できます。
function addPrefix(text, prefix) {
    // prefixがnullまたはundefinedの時、デフォルト値を返す
    const pre = prefix ?? "デフォルト:";
    return pre + text;
}

console.log(addPrefix("文字列")); // => "デフォルト:文字列"
// falsyな値でも意図通りに動作する
console.log(addPrefix("文字列", "")); // => "文字列"
console.log(addPrefix("文字列", "カスタム:")); // => "カスタム:文字列"

OR演算子||)とNullish coalescing演算子(??)の違い

記述方法 評価方法
OR演算子 A || B 左辺のオペランドがfalsyな値の場合に右辺のオペランドを評価する。
Nullish coalescing演算子 A ?? B 左辺のオペランドがnullまたはundefinedの場合に右辺のオペランドを評価する。

仮引数に比べて、呼び出し時の実引数の数が多いとき

関数の仮引数に対して実引数の個数が多い場合、あふれた実引数は単純に無視されます。

// 実引数を2つ受け取り、その実引数を足し算して返すadd関数
function add(x, y) {
    return x + y;
}
console.log(add(1, 3)); // => 4
// 実引数を3つ以上指定した場合、3つ目以降は無視される
console.log(add(1, 3, 5)); // => 4
  • 上記から実引数を3つ以上渡しても無視されてしまうためadd(1, 3)add(1, 3, 5)の結果は同じになります。

呼び出し時の実引数の数が多いときの対応策:可変長引数

関数において引数の数が固定ではなく、任意の個数分引数を受け取りたい場合可変長引数を使います。 可変長引数を使うと関数宣言時に受け取る実引数の個数をあらかじめ固定する必要がなく、呼び出し時に個数の制限なく実引数を渡すことができるようになります。 可変長引数を実現するためには、Rest parametersargumentsという特殊な変数を利用します。

可変長引数を実現する方法①[ES2015] Rest parameters

Rest parametersは、仮引数名の前に...をつけた仮引数のことで、残余引数とも呼ばれます。 Rest parametersには、関数に渡された値が配列として代入されます。Rest parametersを使うことで実引数を何個でも受け取ることができるようになります。

// あらかじめ受け取る実引数の個数を決めないfn関数(実質何個でも実引数を受け取れる)
function fn(...args) {
    // argsは引数の値が順番に入った配列
     return console.log(args);
}
fn("a", "b", "c");  // => [ 'a', 'b', 'c' ]
fn("a", "b", "c", "d", "e", "f"); // => [ 'a', 'b', 'c', 'd', 'e', 'f' ]
fn("a", "b", "c", "d", "e", "f", "g", "h", "i"); // => ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

Rest parametersは、通常の仮引数と組み合わせても定義できます。 ほかの仮引数と組み合わせる際には、必ずRest parameters末尾の仮引数として定義する必要があります。

// 1番目の実引数はarg1で受け取り、2番目以降の実引数は配列として、restArgsで何個でも受け取るfn関数
function fn(arg1, ...restArgs) {
    console.log(arg1); // => "a"
    console.log(restArgs); // => ["b", "c"]
}
fn("a", "b", "c"); // => a  と [ 'b', 'c' ]
fn(); // => undefined  と  []
  • 1番目の実引数はarg1に代入され、残りの実引数がrestArgs配列としてまとめて代入されます。

RestParametersの似ている記述にSpread構文がありますが、使い方に違いがあるので注意しましょう。

構文の意味 使い方
RestParameters 実引数を任意の個数受け取りたい時に実引数を配列としてまとめて受け取る構文です。Rest parametersを関数の仮引数に指定して使います。この時仮引数の前に...をつけます。 ...仮引数
Spread構文 関数呼び出し時に、渡したい実引数を配列としてまとめて渡すことができます。実際には、実引数として渡された配列の展開した要素が関数の仮引数に渡されます。関数を呼び出す際、配列名の前に...をつけて使います。 ...配列
// Spread構文を使った例

function fn(x, y, z) {
    console.log(x); // => 1
    console.log(y); // => 2
    console.log(z); // => 3
}
const array = [1, 2, 3];
// Spread構文で配列を引数に、展開して関数を呼び出す
fn(...array);
// 次のように書いたのと同じ意味
fn(array[0], array[1], array[2]);

可変長引数を実現する方法②arguments

可変長引数を扱う方法として、argumentsという関数の中でのみ参照できる特殊な変数があります。 argumentsは関数に渡された引数の値がすべて入ったArray-likeなオブジェクトです。 Array-likeなオブジェクトは、配列のようにインデックスで要素へアクセスできますが、Arrayのメソッドは利用できないという特殊なオブジェクトです。

function fn() {
    // `arguments`はインデックスを指定して各要素にアクセスできるArray Likeなオブジェクト
    // `arguments`は関数に渡された実引数の値がインデックス番号と紐づいて格納されるオブジェクト(仮引数の定義とは関係なく)
    console.log(arguments[0]); // => "a"
    console.log(arguments[1]); // => "b"
    console.log(arguments[2]); // => "c"
    console.log(arguments);  // => { '0': 'a', '1': 'b', '2': 'c' }
}
fn("a", "b", "c");

argumentsの問題点

  • Arrow Functionでは利用できない

  • Array-likeオブジェクトであるため、Arrayのメソッドを利用できない

  • arguments変数は仮引数の定義とは関係なく、実際に渡された実引数がすべて含まれているオブジェクトであるため、仮引数だけを見て関数が可変長引数を受けつけるのかを判断できない

上記の問題点から可変長引数が必要な場合はarguments変数よりも、Rest parametersで実装するようにしましょう。

[ES2015] 関数の引数と分割代入

関数の引数においても分割代入が利用できます。 分割代入はオブジェクトや配列からプロパティを取り出し、変数として定義し直す構文です。

分割代入を使わない場合

function printUserId(user) {
    // 関数内で、Userオブジェクトのidプロパティにアクセスする
    console.log(user.id); // => 42
}
const user = {
    id: 42
};
printUserId(user); // printUserId関数の引数としてuserオブジェクトを渡す

分割代入を使う場合(オブジェクトvar)

// 第1引数のオブジェクトから`id`プロパティを変数`id`として定義する
// 関数が呼び出された時にuserオブジェクトのidプロパティが分割代入される
// const id = user; つまり、const id = user.id;と同じこと
function printUserId({ id }) {
    // 変数名idでuserオブジェクトのidプロパティにアクセスできるようになる
    console.log(id); // => 42
}
const user = {
    id: 42
};
printUserId(user);
  • 関数の引数における分割代入では、仮引数に定義したい変数名を定義し、実引数として渡すオブジェクトから対応するプロパティを代入します。 const id = user.id;
  • 関数の引数における分割代入は、オブジェクトだけではなく配列についても利用できます。

関数の引数における分割代入(配列var)

// 引数に渡された配列arrayの1番目の要素がfirstに、2番目の要素がsecondに代入(実引数で渡されなかった仮引数にはundifinedが代入)
function print([first, second, third]) {
    console.log(first); // => 1
    console.log(second); // => 2
    console.log(third); // => undefined
}
const array = [1, 2];
print(array);

関数はオブジェクト

JavaScriptでは、関数関数オブジェクトとも呼ばれ、オブジェクトの一種です。 関数はただのオブジェクトとは異なり、関数名()をつけることで、関数としてまとめた処理を呼び出すことができます。 一方で、()をつけて呼び出されなければ、関数をオブジェクトとして参照できる他、変数へ代入したり、関数の引数として渡すなど値として扱うことが可能です。

🌟このように関数が値として扱えることを、ファーストクラスファンクション第一級関数)と呼びます。

関数を値として扱うことができると..? - 関数を変数に代入できる - 関数の引数として渡すことができる(コールバック関数)

関数を値として定義する方法①関数式

関数式とは、関数を値として変数へ代入している式のことを言います。 関数宣言は文でしたが、関数式では関数をとして扱っています。

// 関数式
const 関数名 = function(仮引数) { // 関数式は変数名で参照できるため、"関数名"を省略できる
    // 関数を呼び出したときの処理
    // ...
    return 関数の返り値;
};

// functionキーワード**を使った関数宣言
function 関数名(仮引数) { // 関数宣言では"関数名"は省略できない
    // 関数を呼び出したときの処理
    // ...
    return 関数の返り値;
}
  • 関数式変数名で参照できるため、"関数名"を省略できます。
  • functionキーワードを使った関数宣言では"関数名"は省略できません。
  • 関数式では、名前を持たない関数(匿名関数・無名関数)を変数に代入できます。
  • 関数式で定義した関数は、一般的に変数などに代入するため名前をつけない匿名関数として定義されることが多いです。 しかし一方で、関数式で定義した関数に名前をつけた場合、定義した関数名は関数の中からしか呼べなくなります。

関数を値として定義する方法②[ES2015] Arrow Function

関数式にはfunctionキーワードを使った方法以外に、Arrow Functionと呼ばれる書き方があります。 名前のとおり矢印のような=>アロー記号)を使い、匿名関数を定義する構文です。

// Arrow Functionを使った関数定義
const 関数名 = (仮引数) => {
    // 関数を呼び出したときの処理
    // ...
    return 関数の返す値;
};

Arrow Functionの省略記法

  • 関数の仮引数が1つのときは()を省略できる

  • 関数の処理が1つの式である場合に、ブロック{}return文を省略できる

    • その式の評価結果をreturnの返り値とする
// 仮引数の数と定義
const fnA = () => { /* 仮引数がないとき */ };
const fnB = (x) => { /* 仮引数が1つのみのとき */ };
const fnC = x => { /* 仮引数が1つのみのときは()を省略可能 */ };
const fnD = (x, y) => { /* 仮引数が複数のとき */ };
// 値の返し方
// 次の2つの定義は同じ意味となる
const mulA = x => { return x * x; }; // ブロックの中でreturn
const mulB = x => x * x;            // 1行の式のみの場合はreturnとブロックを省略できる

Arrow Functionの特徴

  • 名前をつけることができない(常に匿名関数
  • thisが静的に決定できる
  • functionキーワードに比べて短く書くことができる
  • newできない(コンストラクタ関数ではない)
  • arguments変数を参照できない(functionキーワードに比べて、人による解釈や実装の違いが生まれにくい)

functionキーワードの関数式でコールバック関数を定義した場合

const array = [1, 2, 3];
// 1,2,3と順番に値が渡されコールバック関数(匿名関数)が処理する
// mapメソッドは、array配列の要素を順番にコールバック関数(匿名関数)へ渡し、そのコールバック関数が返した値を新しい配列にして返します。
const doubleArray = array.map(function(value) {
    return value * 2; // 返した値をまとめた配列ができる
});
console.log(doubleArray); // => [2, 4, 6]
  • mapメソッドの引数としてfunctionキーワードを使った匿名関数(コールバック関数)を定義しています。
  • mapメソッドは、順番にarray配列の要素をコールバック関数に渡し、そのコールバック関数が返した値を新しい配列にして返します。
  • 今回の場合、1,2,3と順番に要素の値が渡され、コールバック関数で要素の値を2倍にして戻り値として返します。 mapメソッドは戻り値を受け取り、要素を2倍にした値を要素とする新たな配列を作成して返します。

Arrow Functionでコールバック関数を定義した場合

const array = [1, 2, 3];
// 仮引数が1つなので`()`を省略できる
// 関数の処理が1つの式なので`return`文を省略できる
const doubleArray = array.map(value => value * 2);
console.log(doubleArray); // => [2, 4, 6]
  • mapメソッドの引数としてArrow Functionを使った匿名関数(コールバック関数)を定義しています。
  • Arrow Functionでは関数の処理が1つの式だけである場合に、return文を省略し暗黙的にその式の評価結果をreturnの返り値とします。
  • Arrow Functionの仮引数が1つである場合は()を省略できます。
  • Arrow Functionを使う方がコードの見通しを良く記述できます。

上記の特徴からArrow Functionは、人による解釈や実装の違いが生まれにくく、thisの問題の多くを解決できるという利点があるため、積極的にArrow Functionを使うようにしましょう。 Arrow Functionを使うことに問題がある場合にのみfunctionキーワードを使いましょう。

同じ名前の関数宣言は上書きされる

関数宣言で定義した関数は、引数の数や型の違いで区別されることなく、関数の名前でのみ区別されます。 そのため、同じ関数名を複数回宣言した場合には、後ろで宣言された同じ関数名によって上書きされます。

(TypeScriptで宣言した関数は、名前以外にも引数の数や型の違いで区別されます。そのため、同じ関数名を複数回宣言した場合にも上書きされることなく、引数の数や型が異なる同じ関数が共存する状態になります。)

// 仮引数の数が異なる同じfn関数を2つ定義
function fn(x) {
    return `最初の関数 x: ${x}`;
}
// 仮引数の定義が異なっていても同じ名前のfn関数なので上書きされる
function fn(x, y) {
    return `最後の関数 x: ${x}, y: ${y}`;
}
// fn関数を呼び出すと、最後に定義されたfn関数が優先されて実行
console.log(fn(2, 10)); // => "最後の関数 x: 2, y: 10"

ただし、同じ関数名で複数の関数を定義することは基本的に意味がないため避けるべきです。

コールバック関数

関数ファーストクラスであるため、匿名関数を関数の引数(値)として渡すことができます。 ある関数やメソッドの引数として渡される関数のことをコールバック関数と呼びます。 一方、コールバック関数を引数として使う関数やメソッドのことを高階関数と呼びます。

function 高階関数(コールバック関数) {
    コールバック関数();
}

コールバック関数は非同期処理においてもよく利用されます。

メソッド

JavaScriptではオブジェクトのプロパティ関数である場合にそれをメソッドと呼びます。JavaScriptにおいて、関数メソッドの機能的な違いはありません。 一般的にはメソッドも含めたもの関数と言い、関数宣言などとプロパティである関数を区別する場合にメソッドと呼びます。

// objのmethod1プロパティとmethod2プロパティに関数を定義した場合、obj.method1プロパティとobj.method2プロパティがメソッドになる
const obj = {
    method1: function(引数) {
        // `function`キーワードでのメソッド
        console.log('Hello World');
    },
    method2: (引数) => {
        // Arrow Functionでのメソッド
        console.log('Good Night');
    }
};
obj.method1();  // => Hello World
obj.method2();  // => Good Night
  • obj.method1プロパティとobj.method2プロパティがメソッドです。
// 空オブジェクトのobjを定義してから、methodプロパティへ関数を代入してもメソッドを定義できる
const obj = {};
obj.method = function() {
    return "this is method";
};
// メソッドを呼び出す際は、オブジェクト.メソッド名()とする
console.log(obj.method()); // => "this is method"
  • 空のオブジェクトobjを定義後、関数を値とするmethodプロパティをobjに代入すると、obj.methodプロパティがメソッドになります。
  • メソッドを呼び出す場合は、関数呼び出しと同様にオブジェクト名.メソッド名()と書くことで呼び出せます。

[ES2015] メソッドの短縮記法

ES2015からは、メソッドの短縮記法が追加されました。短縮記法はオブジェクトのメソッド定義だけではなく、クラスのメソッドを定義する共通の書き方になります。

const obj = {
    key: 'value', // keyとvalueのプロパティ
    method() { // メソッドの短縮記法で定義したメソッド
        // メソッドの処理
        return "this is method";
    }
};
console.log(obj.method()); // => this is method
console.log(obj.key); // => value
  • オブジェクトリテラルの中で メソッド名(){ /*メソッドの処理*/ }と記述するとメソッドの短縮記法が使えます。

メソッドまとめ

名前 関数 メソッド
関数宣言(function fn(){}) x
関数式(const fn = function(){})
Arrow Function(const fn = () => {})
メソッドの短縮記法(const obj = { method(){} }) x

メソッドの定義方法(3種類)

const obj = {
    // `function`キーワードを使ったメソッド
    method1: function() {
    },
    // Arrow Functionを使ったメソッド
    method2: () => {
    },
    // メソッドの短縮記法で定義したメソッド(関数ではない)
    method3() {
    }
};

参考

JSPrimer-関数と宣言