マッスル・メモリー

筋肉エンジニアのブログ

【js】クラスを定義する

ES6からjsでもclassを定義することができるようになりました。

オブジェクト指向に関わる箇所なので、是非マスターしたいところです。

オブジェクト指向については以前記事にまとめてあります。

https://takuma521.hatenablog.com/entry/2019/07/15/214850

クラスの定義

class Anime {
  // コンストラクター
  constructor(options) {
    this.title = options.title;
  }

  // メソッド
  callTitle() {
    return this.title;
  }
}

const anime = new Anime({ title: 'リゼロ' });

console.log(anime);
// Anime {title: "リゼロ"}
console.log(anime.callTitle());
// リゼロ

コンストラクターの名前はconstructorで固定です。

※jsではpublic, protected, privateのようなアクセス修飾子は利用できません。 jsのクラスでは、全てのメンバーがpublicとなります。(どこからでもアクセスできる)

継承

継承とは、クラスの定義の共通する部分を別のクラスにまとめることです。

そうすることで、コードの重複を防ぐことができます。

class Rezero extends Anime {
  constructor(options) {
    super(options);
    this.deathCount = options.deathCount;
  }

  returnToDeath() {
    return `${this.deathCount}回死に戻りました。`;
  }
}

const rezero = new Rezero ({ title: 'リゼロ', deathCount:4 })

console.log(rezero);
// Rezero {title: "リゼロ", deathCount: 4}

console.log(rezero.callTitle());
// リゼロ

console.log(rezero.returnToDeath());
// 4回死に戻りました。

クラスを継承するにはextendsを使います。

Rezeroクラスで定義されたreturnToDeath()メソッドはもちろん、

Animeクラスで定義されていたcallTitle()メソッドも呼び出すことができます。

オーバーライド

基底クラス(Animeクラス)で定義されたメソッド、コンストラクターはサブクラス(Rezeroクラス)で上書きすることもできます。

これを、メソッドのオーバーライドと言います。

今回は基底クラス(Animeクラス)で定義されていたcallTitle()メソッドにサブタイトルも返すようにオーバーライドします。

class Rezero extends Anime {
  constructor(options) {
    super(options);
    this.deathCount = options.deathCount;
    this.subTitle = options.subTitle;
  }

    callTitle() {
      return `${this.title} ${this.subTitle}`;
    }

  returnToDeath() {
    return `${this.deathCount}回死に戻りました。`;
  }
}

const rezero = new Rezero ({ title: 'リゼロ', deathCount:4, subTitle: '18話 ゼロから' })

console.log(rezero);
// Rezero {title: "リゼロ", deathCount: 4, subTitle: "18話 ゼロから"}

console.log(rezero.callTitle());
// リゼロ 18話 ゼロから

console.log(rezero.returnToDeath());
// 4回死に戻りました。

コンストラクターの引数にはオブジェクトを渡しているので、コンストラクターthis.subTitle = options.subTitle;を追記します。

そして、インスタンス化の際にsubTitleを追加します。

callTitle()メソッドの中身を書き換えます。

すると、callTitle()を呼び出すとtitleだけでなく、subTitleも返すようになります。

【js】配列を操作するメソッドまとめ

今回は配列を操作するときに便利なメソッドをまとめます!

forEach

let titles = ['このすば', 'リゼロ', '幼女戦記', 'オバロ']

titles.forEach((title) => {
  console.log(title);
});
// このすば
// リゼロ
// 幼女戦記
// オバロ
function showTitle(title) {
  console.log(title);
}

titles.forEach(showTitle);
// このすば
// リゼロ
// 幼女戦記
// オバロ

気をつけてもらいたいのが、匿名関数を置かずに名前付きの関数を定義して使用する場合に、

titles.forEach(showTitle());
// undefined is not a function

showTitle()とカッコを付けてしまうとエラーになるので注意です。

map

mapを使うと配列から新たな配列を作ることができます。

let titles = ['このすば', 'リゼロ', '幼女戦記', 'オバロ']

let secondSeasonTitles = titles.map((title) => {
  return `${title}2`
});

console.log(secondSeasonTitles);
// ["このすば2", "リゼロ2", "幼女戦記2", "オバロ2"]

mapの中でreturnを忘れないようにしましょう。

filter

filterを使うことで条件に合致したものを返せます。

let characters = [
  { name: 'スバル', gender: 'man' },
  { name: 'レム', gender: 'woman' },
  { name: 'ラム', gender: 'woman' },
  { name: 'ロズワール', gender: 'man' },
];

characters.filter((character) => {
  return character.gender === 'woman';
});
// 0: {name: "レム", gender: "woman"}
// 1: {name: "ラム", gender: "woman"}

もちろん、forEachで記述することも可能ですが、可読性を考えるとforEachを使わない方が、読み手にわかりやすいです。

find

findでは条件に合った最初の要素を返します。

let characters = [
  { name: 'トン' },
  { name: 'チン', id: 1 },
  { name: 'カン' },
  { name: 'チン', id: 2 },
];

characters.find((character) => {
  return character.name === 'チン';
});
// {name: "チン", id: 1}

every, some

everyは全て条件を満たす場合trueを返します。

someはどれかが条件を満たす場合trueを返します。

// 説明
// パワーリフターがsq, bp, dlの合計がjcpStandardRecordという標準記録を超えているかどうか
// 超えるとjcpという大会に出場できる

let powerLifters = [
  { id:1, sq: 200, bp: 140, dl: 230 },
  { id:2, sq: 180, bp: 120, dl: 200 },
  { id:3, sq: 200, bp: 130, dl: 200 },
];

const jcpStandardRecord = 510;

let allParticipantsFlag ; // 全員出場できる
let someParticipantsFlag ; // 誰かは出場できる

allParticipantsFlag = powerLifters.every((powerLifter) => {
  return jcpStandardRecord <= (powerLifter.sq + powerLifter.bp + powerLifter.dl)
});

someParticipantsFlag = powerLifters.some((powerLifter) => {
  return jcpStandardRecord <= (powerLifter.sq + powerLifter.bp + powerLifter.dl)
});

console.log(allParticipantsFlag);
// false

console.log(someParticipantsFlag);
// true

someとincludesの使い分け

someとincludesどちらも、特定の要素が含まれているかをtrue/falseで判定しますが、どう使い分けたらいいのでしょうか?

配列の場合、

const animes = [  'このすば', 'リゼロ', '幼女戦記', 'オバロ']
let targetAnime = 'リゼロ';

animes.some((anime) => {
  return anime
} );
// true

animes.includes(targetAnime);
// true

どちらもtrueを返します。

しかし、オブジェクトの配列の場合は、

const animes = [
  { title: 'このすば', mainCharacter: 'カズマ' },
  { title: 'リゼロ', mainCharacter: 'スバル' },
  { title: '幼女戦記', mainCharacter: 'ターニャ ' },
  { title: 'オバロ', mainCharacter: 'アインズ' }
];

let targetAnime = {
  title: 'リゼロ',
  mainCharacter: 'スバル'
}

animes.some((anime) => {
  return anime === targetAnime;
} );
// false

animes.includes(targetAnime);
//false

どちらもfalseを返します。

実はオブジェクトを比較するときにその比較は同じ参照を指しているかどうかでテストされるのです。

const animes = [
  { title: 'このすば', mainCharacter: 'カズマ' },
  { title: 'リゼロ', mainCharacter: 'スバル' },
  { title: '幼女戦記', mainCharacter: 'ターニャ ' },
  { title: 'オバロ', mainCharacter: 'アインズ' }
];

let targetAnime = animes[1];

animes.includes(targetAnime);
// true

このようにすればincludesを使えますが、

基本的にオブジェクトを比較する場合はsomeを用いて

const animes = [
  { title: 'このすば', mainCharacter: 'カズマ' },
  { title: 'リゼロ', mainCharacter: 'スバル' },
  { title: '幼女戦記', mainCharacter: 'ターニャ ' },
  { title: 'オバロ', mainCharacter: 'アインズ' }
];

animes.some((anime) => {
  return anime.title === targetAnime.title && anime.mainCharacter === targetAnime.mainCharacter;
} );
// true

このようにします。

reduce

reduceは配列を左から右へ2つの値に対して、同時に関数を適用し単一の値にします。

(右から左へはreduceRightという関数があります。)

例えば、合計値を求めることができます。

let numbers = [1, 2, 3, 4, 5]

let sum = numbers.reduce((sum, number) => {
  return sum + number;
}, 0);

console.log(sum);
// 15

reduceでは、

配列.reduce(コールバック関数(前回の結果, 今回取り出した値), 初期値)

とします。

例でいうと初期値は0です。

コールバック関数の第一引数に前回の結果、一番最初は初期値が入ります。

【js】クロージャ

クロージャとは、ローカル変数を参照している関数内関数のことです。

なんのこっちゃわからないので、次のコードを見てください。

function countPoint(init) {
  let point = init;

  return () => {
    return ++point;
  }
}

let myCounter = countPoint(1);

console.log(myCounter()); // 2
console.log(myCounter()); // 3
console.log(myCounter()); // 4
console.log(myCounter()); // 5
console.log(myCounter()); // 6

countPoint関数は戻り値は数値ではなく、数値をインクリメントするアロー関数を返しています。

引数や戻り値が関数である関数のことを高階関数と呼ぶのでした。

このように関数が戻り値として返されて起こることがクロージャの仕組みになります。

通常関数の中で使われたローカル変数(ここでいう変数point)は関数の処理が終了した時点で破棄されるはずです。

しかし、countPoint関数から返されたアロー関数がローカル変数のpointを参照し続けているので、

countPoint関数終了後もローカル変数pointは保持され続けるのです。

スコープチェーンで考えてみると、

  • アロー関数を表すCallオブジェクト
  • countPoint関数のCallオブジェクト
  • グローバルオブジェクト

というスコープチェーンがアロー関数が有効である間は保持されるのです。

別の例を見てみましょう。

function countPoint(init) {
  let point = init;

  return () => {
    return ++point;
  }
}

let myCounter1 = countPoint(1);
let myCounter2 = countPoint(100);

console.log(myCounter1()); // 2
console.log(myCounter2()); // 101
console.log(myCounter1()); // 3
console.log(myCounter2()); // 102

ここでわかるように、呼び出しごとに生成されたCallオブジェクトは別物なのです。

つまり、myCounter1とmyCounter2のCallオブジェクトは別物ですし、

そのCallオブジェクトに属するローカル変数pointもそれぞれ別物なのです。

クロージャとはに付け加えると、一種の記憶域を提供する仕組みでもあると言えます。

まとめ

クロージャとは、

  • ローカル変数を参照している関数内関数のこと
  • 一種の記憶域を提供する仕組みでもある

【js】スコープチェーン

jsではスクリプトの実行時に、内部的にGlobalオブジェクト(グローバルオブジェクト)を生成します。

グローバルオブジェクトとは、グローバル変数やグローバル関数を管理するためにjsが自動的に生成する便宜的なオブジェクトなのです。

グローバル変数/関数ではGlobal.hogeではなく、単に

変数名
関数名(引数, ...)

のように参照することが可能です。

実はローカル変数もオブジェクトのプロパティです。

ローカル変数はActivation Object(通称Callオブジェクト)のプロパティなのです。

Callオブジェクトとは、関数呼び出しの度に、内部的に自動生成されるオブジェクトのことで、

グローバル変数と同様に、関数内で定義されたローカル変数を管理するためにjsが自動的に生成する便宜的なオブジェクトなのです。

このグローバルオブジェクトとCallオブジェクト、プログラマが自ら生成するものではありませんし、呼び出すものでもありません。

意識すらしませんが、これらの存在を理解することで変数が解決されるメカニズムが見えてきます。

それがスコープチェーンです。

スコープチェーンとは、グローバルオブジェクト、Callオブジェクトを生成の順に連結したリストのことをいいます。

jsではこのスコープチェーンの先頭に位置するオブジェクトから順にプロパティ(変数)を検索し、マッチするプロパティが見つかったところでその値を採用しているのです。

var y = 'Global';

function outerFunc() {
  var y = 'LocalOuter';

  function innerFunc() {
    var z = 'LocalInner';
    console.log(z);
    console.log(y);
    console.log(x);
  }
  innerFunc()
}
outerFunc()

// LocalInner
// LocalOuter
// x is not defined

この場合3階層のスコープが存在します。

  1. Callオブジェクト(innerFunc関数のスコープ)
  2. Callオブジェクト(outerFunc関数のスコープ)
  3. グローバルオブジェクトのスコープ

スコープチェーンはチェーンの先頭である一番内側の関数(今回は1. innerFunc関数のスコープ )から、

グローバルオブジェクトにかけて指定された変数を検索していきます。

変数x, y, zがそれぞれどのようにして値が決まるのかを見ていきましょう。

xの場合

1, 2, 3の階層どこにも定義がされていないので未定義エラーです。

yの場合

1では定義されていません。

2では定義されているので、'LocalOuter'が値になります。

zの場合

1で定義されているので、'LocalInner'が値になります。

このようにしてスコープチェーンを理解することで変数名が重複した場合でも規則が見えていきます!

まとめ

  • グローバルオブジェクトとは、グローバル変数やグローバル関数を管理するためにjsが自動的に生成する便宜的なオブジェクト
  • Callオブジェクトとは、ローカル変数を管理するためにjsが自動的に生成する便宜的なオブジェクト
  • スコープチェーンとは、グローバルオブジェクト、Callオブジェクトを生成の順に連結したリストのこと
  • スコープチェーンはチェーンの先頭である一番内側の関数から、グローバルオブジェクトにかけて指定された変数を検索していき、見つけたところで値を採用する。

【js】ES6 タグ付きテンプレート文字列

テンプレート文字列を利用することで、文字リテラルに変数を埋め込めますが、

変数をそのまま埋め込むのではなく、加工した上で埋め込みたいということもあるでしょう。

例えば、

let subaru = '<strong>スバルくん</strong>'
let barusu = '<strong>バルス</strong>'

console.log(`${subaru}素敵です。${barusu}死になさい`);
// <strong>スバルくん</strong>素敵です。<strong>バルス</strong>死になさい

この時に<>タグをエスケープして出力したい場合に、タグ付きテンプレート文字列という機能があります!

function escapeHtml(str) {
  if (!str) { return ''; }
  str = str.replace(/</g, '&lt;');
  str = str.replace(/>/g, '&gt;');
  return str;
}

function e(templates, ...values) {
  let result = '';
  templates.forEach(function(template, index) {
    result += template + escapeHtml(values[index]);
  });
  return result;
}

let subaru = '<strong>スバルくん</strong>'
let barusu = '<strong>バルス</strong>'


console.log(e `${subaru}素敵です。${barusu}死になさい`);

// &lt;strong&gt;スバルくん&lt;/strong&gt;素敵です。&lt;strong&gt;バルス&lt;/strong&gt;死になさい

escapeHtml関数はタグをエスケープしてくれる関数です。

e`${subaru}素敵です。${barusu}死になさい`と見慣れない書き方ですが、

タグ付きテンプレートは

関数名`テンプレート文字列`

と記述します。

タグ付きテンプレートを使うためには関数は以下の条件を満たさないといけません。

  • 引数として以下を受け取ること
    • テンプレート文字列(分解したもの)
    • 埋め込む変数(可変長数)
  • 戻り値として加工済みの文字列を返すこと

この例では引数templates(テンプレート文字列)とvalues(埋め込み変数)とをforEachで交互に出力しています。

その際に、escapeHtml関数でタグをエスケープ処理しています。

forEachで交互に出力とはどういうことかというと

forEachの1周目
templates[0] → ''
values[0] → subaru = '<strong>スバルくん</strong>'

forEachの2周目
templates[1] → '素敵です。'
values[1] → barusu = '<strong>バルス</strong>'

forEachの3周目
templates[2] → '死になさい'
values[2] → ''

となっています。

これにより、テンプレート文字列に影響を与えずに変数の値だけを加工しているのです。

【js】 引数【ES6】と関数の呼び出し方

ES6で追加された関数の引数の記述と関数の呼び出し方についてまとめていきます!

引数

引数のデフォルト値

ES6で引数のデフォルト値を設定するには、

仮引数 = デフォルト値

とするだけです。

function getTrainingVolume(weight = 80, rep = 10, set = 3) {
  return weight * rep * set;
};

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume()}kgです`);
// ベンチプレスのトレーニングボリュームは2400kgです

また、デフォルト値にはリテラルだけでなく、他の引数や、関数の結果も指定できます。

function multi(a, b = a) {
  return a * b;
};

console.log(multi(10, 5)); // 50
console.log(multi(3)); // 9

ただし、他の引数をデフォルト値とする場合、参照できるのは自身より前に定義されたものだけです。

function multi(a = b, b = 5) {
  return a * b;
};

console.log(multi());
// Cannot access 'b' before initialization

これだと、bがまだ定義されていないのにbをaに代入しようとしているので、エラーになってしまいます。

デフォルト値を使う場合の注意点

デフォルト値が適用される場合されない場合

デフォルト値が適用されるのは、引数が明示的に渡されなかった場合です。

null, false, 0, ''を渡した場合では、デフォルト値は適用されません。

function getTrainingVolume(weight = 80, rep = 10, set = 3) {
  return weight * rep * set;
};

console.log(getTrainingVolume(1, 1, null)); // 0

console.log(getTrainingVolume(1, 1, false)); // 0

console.log(getTrainingVolume(1, 1, 0)); // 0

console.log(getTrainingVolume(1, 1, '')); // 0

console.log(getTrainingVolume(1, 1, undefined)); // 3

80 * 10 * null では結果は0になります。

しかし、undefinedを渡した場合は、デフォルト値が適用されます。

デフォルト値を持つ仮引数は、引数リストの末尾に

デフォルト値が末尾に来ない関数を呼び出す際に引数を1つしか渡さなかったらどうなるでしょうか?

function multi(a = 5, b) {
  return a * b;
};

console.log(multi(10)); // NAN

結果は、10 * undefinedで NAN(not a number)になります。

つまり、引数aに10が渡され、bはデフォルト値を持たないのでundefinedとみなされるのです。

このようのなことが起こらないように、デフォルト値を持つ引数の後に持たない引数を記述しないようにしましょう。

必須の引数を宣言する

jsでは引数にデフォルト値が宣言されているかどうかが、引数の必須 or 任意を表すわけではありません。

デフォルト値を持たない引数に、値を渡さなかったからといって「引数が足りない!」と警告してくれるわけではないのです。

もし引数の必須を表現したいのならば、以下のように記述する必要があります。

function required() {
  throw new Error('引数が足りない!');
}

function square(a = required()) {
  return a ** 2;
};

console.log(square(3)); // 9

console.log(square()); // Uncaught Error: 引数が足りない!

引数が指定されなかった場合にrequired関数が実行され例外がスローされます。

可変長引数の関数を定義する

可変長引数の関数とは、引数の個数があらかじめ決まっていない関数のことです。

可変長引数を使わなくても、呼び出す時の引数に配列を指定する方法を使えば同じ事ができます。

function sum(...numbers) {
  return numbers.reduce((sumNumber, number) => {
    return sumNumber + number;
  }, 0);
};

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5, 6)); // 21

...(rest演算子)を使用することで、引数をいくつ渡したとしても対応してくれます。

関数の呼び出し方

関数自身を呼び出す 再起関数

再帰関数とは、ある関数が自分自身を呼び出すこと、またはそのような関数のことです。

function sumNumber(number) {
  if (number != 0) {return number + sumNumber(number-1); }
  return 0;
};

sumNumber(2); // 3
sumNumber(100); // 5050

sumNumberでは、引数に受け取った値から-1ずつしていきその合計を返す関数です。

例えば、5を渡したら、5 + 4 + 3 + 2 + 1 を計算します。

この関数では、内部で自分自身を再帰的にに呼び出しています。

再帰関数で注意すべきは終了点があることです。

今回だと引数が0になると0を返し終了しますが、この処理を書かないと無限ループになってしまいます。

気をつけましょう

関数の引数も関数 高階関数

高階関数とは、関数を引数、戻り値として扱う関数です。

function operateArray(data, f) {
  for (let key in data) {
    f(data[key], key);
  }
}

function showElement(value, key) {
  console.log(`${Number(key) + 1}番目の値は「${value}」です`);
}

let ary = [10, 20, 30, 40, 50];
operateArray(ary, showElement);
// 1番目の値は「10」です
// 2番目の値は「20」です
// 3番目の値は「30」です
// 4番目の値は「40」です
// 5番目の値は「50」です

関数operateArrayは引数にdataと関数fをとり、fにしたがって処理を行う高階関数です。

ここではshowElement関数が引数となっています。

この高階関数operateArray関数で使われているshowElementのような関数をコールバック関数といいます。

高階関数を使うことで、コールバック関数を変えることにより処理のベースは変えずに具体的な処理内容だけ差し替えることができます。

このコールバック関数はその場限りの使い捨ての場合が多いため、匿名関数として記述した方がコードがシンプルになります。

function operateArray(data, f) {
  for (let key in data) {
    f(data[key], key);
  }
}

let ary = [10, 20, 30, 40, 50];
operateArray(
  ary,
  (value, key) => {
    console.log(`${Number(key) + 1}番目の値は「${value}」です`);
  }
);
// 1番目の値は「10」です
// 2番目の値は「20」です
// 3番目の値は「30」です
// 4番目の値は「40」です
// 5番目の値は「50」です

このようにコードが短くなっただけでなく、一度限りしか使用しない関数に名前を付けずに済むので、

名前の重複を防ぐことができます!

まとめ

引数のデフォルト値

function getTrainingVolume(weight = 80, rep = 10, set = 3) {
  return weight * rep * set;
}

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume()}kgです`);
// ベンチプレスのトレーニングボリュームは2400kgです
  • null, false, 0, ''を渡した場合では、デフォルト値は適用されない
  • デフォルト値を持つ引数の後に持たない引数を記述しない

可変長引数の関数の定義

function sum(...numbers) {
  return numbers.reduce((sumNumber, number) => {
    return sumNumber + number;
  }, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5, 6)); // 21
  • ...(rest演算子)を使用することで、引数をいくつ渡したとしても対応してくれる

再帰関数

function sumNumber(number) {
  if (number != 0) {return number + sumNumber(number-1); }
  return 0;
};

sumNumber(2); // 3
sumNumber(100); // 5050
  • 再帰関数とは、ある関数が自分自身を呼び出すこと、またはそのような関数のこと
  • 終了点を書かないと無限ループになってしまう

高階関数

function operateArray(data, f) {
  for (let key in data) {
    f(data[key], key);
  }
}

let ary = [10, 20, 30, 40, 50];
operateArray(
  ary,
  (value, key) => {
    console.log(`${Number(key) + 1}番目の値は「${value}」です`);
  }
);
// 1番目の値は「10」です
// 2番目の値は「20」です
// 3番目の値は「30」です
// 4番目の値は「40」です
// 5番目の値は「50」です
  • 高階関数とは、関数を引数、戻り値として扱う関数
  • コードが短くできる
  • 名前の重複を防げる

【js】 4種類の関数の定義の仕方

Javascriptには関数を定義する方法が4種類あります。

今回はそれらの定義の仕方、特徴についてまとめていきます。

1. function命令で定義する

一番ベーシッックな関数の定義の仕方です。

function getTrainingVolume(weight, rep, set) {
  return weight * rep * set;
};

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです

なお戻り値を返さない関数はreturnを省略できますが、その場合はundefinedを返します。

またこのfunction命令での関数の定義には1つ特徴があります。

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです

function getTrainingVolume(weight, rep, set) {
  return weight * rep * set;
};

先ほどのコードと内容は同じですが、宣言する前にgetTrainingVolumeを実行しています。

これはまだ関数が定義されていない状態ですので、エラーになるはずです。

しかし、結果は問題なく実行されます。

実はfunction命令は静的な構造を宣言するためのキーワードであるからなのです。

静的な構造という言葉が難しいですが、要は、

function命令はコードを解析/コンパイルするタイミングで関数を登録している。

のです。

なので、実行時にすでにgetTrainingVolume関数がどこからでも呼び出せる状態なのです。

2. Functionコンストラクター経由で定義する

let getTrainingVolume = new Function('weight', 'rep', 'set', 'return weight * rep * set;');

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです

2は引数や関数本体を文字列として定義できるという特徴があるだけで基本使われません。

1. function命令で定義する

3. 関数リテラル表現で定義する

4. アロー関数で定義する(ES6)

で関数は基本的に定義すると覚えましょう。

3. 関数リテラル表現で定義する

リテラルとは、データ型に格納できる値そのものです。

例えば、

- 文字リテラル 'hello, world'

- 数値リテラル(10進数リテラル) 100

などです。

つまり、関数リテラルとは

文字列や数値と同ように変数に代入したり、ある関数の引数に渡したり、戻り値として関数を返すことが可能ということです。

let getTrainingVolume = function(weight, rep, set) {
  return weight * rep * set;
};

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです

このように宣言します。

また、1と同じように関数が宣言される前に実行させるとどうなるでしょうか?

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// Cannot access 'getTrainingVolume' before initialization

let getTrainingVolume = function(weight, rep, set) {
  return weight * rep * set;
};

すると、エラーが吐かれます。

1と3の違いをまとめるとこうです。

1. function命令での定義

→関数getTrainingVolumeを直接定義している

→コードを解析/コンパイルするタイミングで関数を登録しているのでコードのどこからでも呼び出せる。

3. 関数リテラル表現での定義

→名前のない関数を定義した上で変数getTrainingVolumeに格納している

→実行時に評価されるので、呼び出し元のコードより前で関数を定義しなくてはいけない。(2のFunctionコンストラクターも同じ)

関数リテラルは宣言した時点では、名前を持たないことから匿名関数、または無名関数と呼ばれることがあります。

4. アロー関数で定義する(ES6)

アロー関数を利用することで関数リテラルをよりシンプルに記述できます。

let getTrainingVolume = (weight, rep, set) => {
  return weight * rep * set;
};

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです

アロー関数ではfunctionキーワードは書きません。

その代わりに=>アローで引数と関数をつなげます。

アロー関数ではさらにシンプルに書ける場合があります。

処理が1行の場合は

let getTrainingVolume = (weight, rep, set) => weight * rep * set;

さらに、引数が1つの場合は、

let getSquare = sideLength => sideLength ** 2 ;

ただし、引数がない場合は()を省略できません。

let sayHello = () => console.log('hello, world') ;

まとめ

1. function命令で定義する

function getTrainingVolume(weight, rep, set) {
  return weight * rep * set;
};

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです
  • 関数getTrainingVolumeを直接定義している
  • 実行時にすでにgetTrainingVolume関数がどこからでも呼び出せる状態

2. Functionコンストラクター経由で定義する

let getTrainingVolume = new Function('weight', 'rep', 'set', 'return weight * rep * set;');

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです
  • 引数や関数本体を文字列として定義できる
  • 実行時に評価されるので、呼び出し元のコードより前で関数を定義しなくてはいけない
  • 基本使わない

3. 関数リテラル表現で定義する

let getTrainingVolume = function(weight, rep, set) {
  return weight * rep * set;
};

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです
  • 匿名関数、または無名関数を定義した上で変数getTrainingVolumeに格納している
  • 実行時に評価されるので、呼び出し元のコードより前で関数を定義しなくてはいけない

4. アロー関数で定義する(ES6)

let getTrainingVolume = (weight, rep, set) => {
  return weight * rep * set;
};

console.log(`ベンチプレスのトレーニングボリュームは${getTrainingVolume(100, 8, 5)}kgです`);
// ベンチプレスのトレーニングボリュームは4000kgです

処理が1行の場合

let getTrainingVolume = (weight, rep, set) => weight * rep * set;

処理が1行、引数が1つの場合

let getSquare = sideLength => sideLength ** 2 ;

処理が1行、引数がない場合

let sayHello = () => console.log('hello, world') ;