JavaScriptの基礎 - アロー関数

提供: MochiuWiki : SUSE, EC, PCB

概要

アロー関数 (Arrow Function) は、ES2015 (ES6)で導入された関数定義の新しい構文である。
=> 記号を使った簡潔な記法により、従来の function キーワードによる関数式をより短く記述できる。

アロー関数の最大の特徴は、自身の this を持たない点である。
通常の関数は呼び出し方によって this の値が変化するが、アロー関数は定義された場所 (レキシカルスコープ) の this を継承する。

この特性により、コールバック関数内で this が予期せず変わるという従来の問題を自然に解決できる。

また、単一の式を返す場合は中括弧とreturnキーワードを省略できる 暗黙return (implicit return) の機能も持つ。
これにより、mapfilter 等の配列メソッドと組み合わせた時に簡潔なコードを記述することができる。

一方で、アロー関数は全ての場面で通常の関数の代替として使えるわけではない。
オブジェクトメソッドの定義、コンストラクタとしての使用、arguments オブジェクトの参照が必要な場面ではアロー関数を使用できない。

ReactをはじめとするモダンなJavaScriptフレームワークでは、コンポーネント定義、イベントハンドラ、配列メソッドとの組み合わせにおいてアロー関数が最頻出の関数定義形式となっている。
アロー関数の構文、制約、通常関数との使い分けを正確に理解することは、現代的なJavaScript開発において必須の知識である。


基本構文

基本的な記法

アロー関数の基本的な構文はブロック構文であり、通常の関数式に相当する。
中括弧内に処理を記述し、値を返す場合は明示的にreturnを記述する。

 // 基本的なブロック構文
 (<引数1>, <引数2>) => {
    // 処理
    return <戻り値>;
 }


通常の関数式とアロー関数の対比を以下に示す。

 // 通常の関数式
 const add = function(a, b) {
    return a + b;
 };
 
 // アロー関数 (ブロック構文)
 const addArrow = (a, b) => {
    return a + b;
 };
 
 console.log(add(3, 5));       // 8
 console.log(addArrow(3, 5));  // 8


複数の処理が必要な場合は、ブロック構文を使用する。

 const greet = (name) => {
    const message = "こんにちは、" + name + "さん";
    console.log(message);
    return message;
 };
 
 greet("Alice");  // "こんにちは、Aliceさん"


引数の省略記法

アロー関数では、引数の数に応じて括弧を省略できる場合がある。

引数が1つの場合、括弧を省略することができる。

 // 引数が1つ: () を省略可能
 const double = x => {
    return x * 2;
 };
 
 // 括弧あり (どちらでも動作する)
 const doubleWithParens = (x) => {
    return x * 2;
 };
 
 console.log(double(5));           // 10
 console.log(doubleWithParens(5)); // 10


引数が0個の場合、括弧は省略できず必須である。

 // 引数が0個: () は必須
 const sayHello = () => {
    return "Hello!";
 };
 
 console.log(sayHello()); // "Hello!"


引数が複数の場合、括弧は必須である。

 // 引数が複数: () は必須
 const multiply = (a, b) => {
    return a * b;
 };
 
 const sum3 = (a, b, c) => {
    return a + b + c;
 };
 
 console.log(multiply(4, 5));    // 20
 console.log(sum3(1, 2, 3));     // 6



省略記法

単一式の暗黙return

アロー関数では、関数本体が単一の式である場合に中括弧とreturnキーワードを省略できる。
式の評価結果が自動的に戻り値となる。
これを、暗黙return (implicit return) と呼ぶ。

 // ブロック構文 (明示的 return)
 const add = (a, b) => {
    return a + b;
 };
 
 // 式本体 (暗黙 return)
 const addShort = (a, b) => a + b;
 
 console.log(add(3, 4));      // 7
 console.log(addShort(3, 4)); // 7


暗黙return が使えるのは式のみであり、if 文や for 文等の文は使用できない。

 // 式: 暗黙returnが使用できる
 const isEven = n => n % 2 === 0;
 const getName = user => user.name;
 
 console.log(isEven(4));  // true
 console.log(isEven(3));  // false


配列の mapfilter との組み合わせで、暗黙return の効果が際立つ。

 const numbers = [1, 2, 3, 4, 5];
 
 // 通常の関数式
 const doubled1 = numbers.map(function(n) {
    return n * 2;
 });
 
 // アロー関数 (暗黙 return)
 const doubled2 = numbers.map(n => n * 2);
 
 // filter との組み合わせ
 const evens = numbers.filter(n => n % 2 === 0);
 
 console.log(doubled2);  // [2, 4, 6, 8, 10]
 console.log(evens);     // [2, 4]


オブジェクトリテラルの返却

アロー関数で 暗黙return を使用してオブジェクトリテラルを返す場合は、括弧で囲む必要がある。
中括弧 {} はブロックとして解釈されるため、そのまま記述するとエラーとなる。

 // 正しい書き方: () で囲んでオブジェクトリテラルとして扱う
 const getUser2 = (name, age) => ({ name: name, age: age });
 
 // 誤った書き方: {} がブロックと解釈され、undefined が返る
 const getUser1 = (name, age) => { name: name, age: age };
 // SyntaxError または undefined

 // プロパティ短縮記法との組み合わせ
 const getUser3 = (name, age) => ({ name, age });
 
 console.log(getUser2("Alice", 30));  // { name: "Alice", age: 30 }
 console.log(getUser3("Bob", 25));    // { name: "Bob", age: 25 }


配列メソッドでオブジェクトを返す時の例を以下に示す。

 const users = ["Alice", "Bob", "Charlie"];
 
 // 正: () で囲む
 const result2 = users.map(name => ({ id: name.length, name: name }));
 
 // 誤: {} がブロックとして解釈される
 // const result1 = users.map(name => { id: name.length, name: name });
 
 console.log(result2);
 // [
 //    { id: 5, name: "Alice" },
 //    { id: 3, name: "Bob" },
 //    { id: 7, name: "Charlie" }
 // ]


省略記法の使い分け

ブロック構文と式本体のどちらを使うかは、処理の内容と可読性を基準に判断する。

アロー関数の構文選択基準
構文 使用場面
式本体 (暗黙 return) 単一の式を評価して返すだけの処理
mapfilterfind 等の配列メソッドのコールバック
シンプルな変換処理や条件式
ブロック構文 複数の処理ステップが必要な場合
変数への代入や条件分岐が必要な場合
console.log 等の副作用を伴う処理


 // 式本体が適切な場面 (シンプルな変換)
 const prices = [100, 200, 300];
 const taxIncluded = prices.map(price => price * 1.1);
 const expensive    = prices.filter(price => price >= 200);
 
 // ブロック構文が適切な場面 (複数の処理)
 const formatPrice = (price) => {
    const taxed = Math.floor(price * 1.1);
    return taxed.toLocaleString() + "円";
 };
 
 console.log(taxIncluded);          // [110, 220, 330]
 console.log(formatPrice(1000));    // "1,100円"



thisを束縛しない特性

レキシカルthis

アロー関数は自身の this を作成しない。
代わりに、アロー関数が定義された場所 (レキシカルスコープ) の this を継承して使用する。

この特性を、レキシカルthisと呼ぶ。

 const obj = {
    name: "Alice",
    // 通常のメソッド: thisはobjを参照
    greet: function() {
       console.log("Hello, " + this.name);  // "Hello, Alice"
    },
    // アロー関数はレキシカルthisを継承する
    // (このケースでは外側のthisを参照)
 };


アロー関数内の this は、関数が定義された時点で確定し、呼び出し方によって変化しない。

通常関数でのthis問題

通常の関数では、this の値は呼び出し方によって動的に決まる。
メソッド内でコールバック関数や setTimeout を使用する時に、this が予期せずグローバルオブジェクト (または、undefined) になるという問題が発生する。

 function Timer() {
    this.count = 0;
 
    // 問題のあるコード: 通常関数のコールバック
    setTimeout(function() {
       this.count++;  // this は window または undefined
       console.log(this.count);  // NaN または TypeError
    }, 300);
 }
 
 const timer = new Timer();


ES2015以前は、この問題を解決するために以下の2つのパターンが広く使用されていた。

 // パターン1: self = this パターン
 function Timer1() {
    this.count = 0;
    const self = this;  // thisを変数に保存

    setTimeout(function() {
       self.count++;  // selfを通じてアクセス
       console.log(self.count);  // 1
    }, 300);
 }
 
 // パターン2: .bind(this) メソッド
 function Timer2() {
    this.count = 0;
 
    setTimeout(function() {
       this.count++;
       console.log(this.count);  // 1
    }.bind(this), 300);  // thisをバインド
 }


アロー関数による解決

アロー関数を使用すると、this の問題を自然かつ簡潔に解決できる。
アロー関数は外側の this (この場合、Timer インスタンス) を継承するため、追加の工夫が不要である。

 function Timer() {
    this.count = 0;
 
    // アロー関数: 外側の this を継承する
    setTimeout(() => {
       this.count++;  // this は Timer インスタンスを参照
       console.log(this.count);  // 1
    }, 300);
 }
 
 const timer = new Timer();


メソッド内のコールバックでも同様に this が保持される。

 const counter = {
    count: 0,
    items: [1, 2, 3],
 
    // アロー関数コールバック: thisがcounterを参照
    increment: function() {
       this.items.forEach(item => {
          this.count += item;  // this は counter を参照
       });
       console.log(this.count);  // 6
    }
 };
 
 counter.increment();



アロー関数の制約

使用できない場面

アロー関数には、通常の関数と異なる制約があり、以下の場面では使用できない または 使用すべきでない。

アロー関数の使用制約
制約事項 説明
オブジェクトメソッドの定義 アロー関数の this はオブジェクト自身を参照しない。
メソッドには通常の関数定義またはメソッド短縮記法を使用する。
コンストラクタとしての使用 new キーワードでアロー関数を呼び出すと TypeError が発生する。
argumentsオブジェクトの参照 アロー関数は arguments オブジェクトを持たない。
可変長引数が必要な場合は残余引数 (...args) を使用する。
動的thisが必要な場面 addEventListener 等でコールバックの this をイベントターゲットにする場合はアロー関数は不適切


各制約のコード例

オブジェクトメソッドでの制約を以下に示す。

 const person = {
    name: "Alice",
 
    // 正: 通常のメソッド定義
    greetFunction: function() {
       console.log("Hello, " + this.name);  // "Hello, Alice"
    },
 
    // 正: メソッド短縮記法 (推奨)
    greetShorthand() {
       console.log("Hello, " + this.name);  // "Hello, Alice"
    }
 
    // 誤: アロー関数のthisはグローバルを参照
    greetArrow: () => {
       console.log("Hello, " + this.name);  // undefined (グローバル)
    },
 };
 
 person.greetArrow();      // "Hello, undefined"
 person.greetFunction();   // "Hello, Alice"
 person.greetShorthand();  // "Hello, Alice"


コンストラクタとしての制約を以下に示す。

 // 通常の関数: コンストラクタとして使用できる
 function RegularFunction(name) {
    this.name = name;
 }
 const obj1 = new RegularFunction("Alice");  // OK
 
 // アロー関数: コンストラクタとして使用できない
 const ArrowFunction = (name) => {
    this.name = name;
 };
 
 const obj2 = new ArrowFunction("Bob");  // TypeError: ArrowFunction is not a constructor


arguments オブジェクトの制約を以下に示す。

 // 通常の関数: arguments オブジェクトを持つ
 function regularSum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
       total += arguments[i];
    }
    return total;
 }
 
 // アロー関数: arguments は使用できない
 const arrowSum = () => {
    // console.log(arguments);  // ReferenceError: arguments is not defined
 };
 
 // アロー関数での代替: 残余引数を使用
 const arrowSumFixed = (...args) => {
    return args.reduce((total, n) => total + n, 0);
 };
 
 console.log(regularSum(1, 2, 3));    // 6
 console.log(arrowSumFixed(1, 2, 3)); // 6


addEventListener での制約を以下に示す。

 const button = document.querySelector("button");
 
 // 正: 通常の関数でthisをイベントターゲットとして使用
 button.addEventListener("click", function() {
    console.log(this);                 // ボタン要素 (押下された要素)
    this.classList.add("clicked");     // OK
 });

 // 誤: アロー関数のthisは外側のスコープを参照
 button.addEventListener("click", () => {
    console.log(this);                 // window (外側のthis)
    // this.classList.add("clicked");  // エラーの可能性
 });



通常関数との比較

下表に、アロー関数と通常関数の主な違いを示す。

アロー関数と通常関数の比較
特性 アロー関数 通常関数
this レキシカル (定義場所のthisを継承) 呼び出し方に依存して動的に変化
arguments なし (残余引数 (...args) を使用) あり
new可否 不可 (TypeError) 可能
ホイスティング なし (変数のルールに従う) 関数宣言のみあり
prototype なし あり
構文 () => {} function() {}
メソッド定義 非推奨 (thisの問題) 推奨



Reactでの使用場面

コンポーネント定義

Reactでは、関数コンポーネントをアロー関数で定義することが一般的なパターンである。

 import React, { useState } from "react";
 
 // アロー関数によるコンポーネント定義
 const App = () => {
    return <h1>Hello, World!</h1>;
 };
 
 // propsを受け取るコンポーネント
 const Greeting = ({ name }) => {
    return <h2>こんにちは{name}さん</h2>;
 };
 
 // 暗黙returnを使用した簡潔な記法 (JSXを括弧で囲む)
 const SimpleButton = ({ label }) => (
    <button>{label}</button>
 );
 
 export default App;


イベントハンドラ

Reactのイベントハンドラとして、アロー関数はインラインで記述することが多い。
アロー関数を使用することにより、外側の this (クラスコンポーネントの場合) や スコープ内の変数に自然にアクセスできる。

 import React, { useState } from "react";
 
 const Counter = () => {
    const [count, setCount] = useState(0);
 
    // アロー関数でイベントハンドラを定義
    const handleIncrement = () => {
       setCount(count + 1);
    };
 
    return (
       <div>
          <p>カウント: {count}</p>
          {/* インラインのアロー関数 */}
          <button onClick={() => setCount(count + 1)}>+1</button>
          {/* ハンドラ関数を渡す (関数を渡すのであって、呼び出さない) */}
          <button onClick={handleIncrement}>+1 (ハンドラ使用)</button>
          {/* 誤: () をつけると即時呼び出しになる */}
          {/* <button onClick={handleIncrement()}>+1</button> */}
       </div>
    );
 };


イベントハンドラでは、関数を渡す時に () を不可しないことが重要である。
onClick={handleClick} は関数の参照を渡すが、onClick={handleClick()} はレンダリング時に即座に関数を呼び出してしまう。

配列メソッドとの組み合わせ

Reactでデータをリストとして表示する時、map と アロー関数の組み合わせが頻繁に使用される。

 import React from "react";
 
 const ItemList = ({ items }) => {
    return (
       <ul>
          {/* map とアロー関数でリスト要素を生成 */}
          {items.map(item => (
             <li key={item.id}>{item.name}</li>
          ))}
       </ul>
    );
 };
 
 const FilteredList = ({ items }) => {
    // filter と map を組み合わせてアクティブな項目のみ表示
    const activeItems = items
       .filter(item => item.isActive)
       .map(item => (
          <li key={item.id}>{item.name}</li>
       ));
 
    return <ul>{activeItems}</ul>;
 };
 
 // 使用例
 const sampleItems = [
    { id: 1, name: "リンゴ",   isActive: true  },
    { id: 2, name: "バナナ",   isActive: false },
    { id: 3, name: "チェリー", isActive: true  }
 ];



関連情報