4.

JavaScript の関数|アロー関数と this・引数・クロージャ

編集
この記事の要点
  • 書き方は関数宣言・関数式・アロー関数の 3 つ。巻き上げと this の扱いが違う
  • アロー関数は自分の this を持たない。外側の this をそのまま使う
  • メソッドとして使う関数をアローで書くと this が意図しないものになる
  • 引数は足りなければ undefined、多くてもエラーにならない
  • 関数は値。変数に入れ、引数として渡し、戻り値として返せる(高階関数)

3 つの書き方

// 1. 関数宣言(巻き上げられるので定義前に呼べる)
function add(a, b) {
  return a + b;
}

// 2. 関数式(変数に入れる。定義前には呼べない)
const sub = function (a, b) {
  return a - b;
};

// 3. アロー関数(最も短い。this を持たない)
const mul = (a, b) => a * b;

// 引数 1 個ならかっこを省略できる(Prettier は付ける流儀)
const double = (n) => n * 2;

// 複数行なら波かっこ + return
const div = (a, b) => {
  if (b === 0) throw new Error("0 で割れません");
  return a / b;
};

// オブジェクトを返すときは丸かっこで囲む
const makeUser = (name) => ({ name, createdAt: Date.now() });

(name) => { name } と書くとブロックとして解釈され undefined が返ります。オブジェクトを返したいときは ({ ... }) と丸かっこで囲みます。

引数の扱い

function greet(name, greeting = "こんにちは") {   // 既定値
  return `${greeting} ${name} さん`;
}

greet("田中");              // こんにちは 田中 さん
greet("田中", "やあ");       // やあ 田中 さん
greet();                    // こんにちは undefined さん(エラーにならない)
greet("田中", "やあ", 1);    // 余分な引数は無視される

// 既定値が使われるのは undefined のときだけ
greet("田中", undefined);   // こんにちは(既定値が使われる)
greet("田中", null);        // null 田中 さん(null は「値がある」扱い)

// 可変長はレストパラメータで受ける
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3);               // 6

// 配列を展開して渡す
const args = [1, 2, 3];
sum(...args);               // 6

引数が多いときはオブジェクトで受ける

// 呼び出し側で何を渡しているか分からない
createUser("田中", 30, true, false, "東京");

// 名前付きにすると読める。順序も自由になる
function createUser({ name, age = 0, isAdmin = false, city = "" }) {
  // ...
}
createUser({ name: "田中", city: "東京", age: 30 });

this の違い

const counter = {
  count: 0,

  incBad: () => {
    this.count++;          // this はここでは counter ではない
  },

  incGood() {              // メソッド短縮記法
    this.count++;          // this は counter
  },

  startBad() {
    setTimeout(function () {
      this.count++;        // this が undefined / window になる
    }, 100);
  },

  startGood() {
    setTimeout(() => {
      this.count++;        // アローなので外側(startGood)の this を使う
    }, 100);
  },
};
状況使うもの
オブジェクトのメソッドメソッド短縮記法incGood() {}
コールバック(setTimeout / map / イベント)アロー関数
addEventListenerthis に要素が欲しい通常の関数(アローだと要素にならない)
クラスのメソッドを渡すbind(this) かアローでラップする
class Button {
  constructor(el) {
    this.count = 0;
    // メソッドをそのまま渡すと this を失う
    el.addEventListener("click", this.onClick);          // NG
    el.addEventListener("click", this.onClick.bind(this)); // OK
    el.addEventListener("click", () => this.onClick());   // OK
  }

  onClick() {
    this.count++;
  }
}

関数は値として扱える

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

nums.map((n) => n * 2);                 // [2, 4, 6, 8]
nums.filter((n) => n % 2 === 0);        // [2, 4]
nums.reduce((acc, n) => acc + n, 0);    // 10
nums.find((n) => n > 2);                // 3
nums.some((n) => n > 3);                // true
nums.every((n) => n > 0);               // true

// 関数を返す関数
function multiplier(factor) {
  return (n) => n * factor;             // factor を覚えている(クロージャ)
}
const triple = multiplier(3);
triple(5);                              // 15

// 処理そのものを差し替えられる
function retry(fn, times = 3) {
  for (let i = 0; i < times; i++) {
    try { return fn(); } catch (e) { if (i === times - 1) throw e; }
  }
}

クロージャ

function createCounter() {
  let count = 0;                 // 外から触れない
  return {
    increment: () => ++count,
    get value() { return count; },
  };
}

const c = createCounter();
c.increment();
c.increment();
console.log(c.value);            // 2
console.log(c.count);            // undefined(隠されている)

関数は定義されたときの変数を覚えたまま持ち歩きます。これを使うと、外部から書き換えられない状態を作れます。

非同期の関数

async function fetchUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

const fetchUser2 = async (id) => {      // アローでも書ける
  const res = await fetch(`/api/users/${id}`);
  return res.json();
};

// async 関数は必ず Promise を返す
fetchUser(1).then(console.log).catch(console.error);

// 呼ぶ側も await するか then でつなぐ
try {
  const user = await fetchUser(1);
} catch (e) {
  console.error(e);
}

async 関数の戻り値は常に Promise です。const user = fetchUser(1) と書くと Promise そのものが入ります。詳細は 非同期処理 を参照してください。

よくあるつまずき

症状原因
undefined が返るアロー関数で波かっこを使い return を書いていない
Cannot read properties of undefined (reading 'count')this を失っている。アローか bind を使う
f is not a function関数式を定義前に呼んでいる/名前のタイプミス
コールバックが即実行されるonClick() と書いている。かっこを外して渡す
arguments が使えないアロー関数は持たない。...args を使う

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 記述方法
  2. コメント
  3. 変数の宣言
  4. 関数
  5. 演算子
  6. 条件文
  7. 配列
  8. 連想配列
  9. ループ処理
  10. 非同期処理
  11. 同期処理
  12. 確認ウィンドウを表示する方法
  13. 文字の置換
  14. base urlを取得する方法
  15. formのsubmit前にjavascriptを呼び出す方法
  16. undefinedのイコール判定
  17. Javascript のみで form を post で submit する方法

最近更新/作成されたページ