JavaScript 入門

JS アロー関数 vs 従来の関数

ES6 (ECMAScript 6) で導入された アロー関数 (Arrow Functions) は、JavaScript において 関数式 (Function Expressions) を記述するためのより簡潔な シンタックス (Syntax) を提供します。

これらは関数を定義するためのよりクリーンで読みやすい方法を提供しますが、従来の関数との決定的な違いを理解することが非常に重要です。これらの違いは単なる構文の差異にとどまらず、thisバインド (Bind) 方式、引数の処理方法、そして特定の ユースケース (Use Case) における適正にまで影響を与えます。

これらの違いをマスターすることで、より効率的で メンテナンス (Maintenance) 性の高い JavaScript コードを記述できるようになります。

1. 構文の違い

アロー関数と従来の関数の最も明白な違いは、その構文にあります。

1.1 従来の関数の構文

従来の関数は function キーワードを使用して定義され、その後に関数名(関数式の場合はオプショナル)、引数用の丸括弧、そして波括弧で囲まれたコードブロックが続きます。

// 関数宣言 (Function Declaration)
function add(x, y) {
  return x + y;
}

// 関数式 (Function Expression)
const multiply = function(x, y) {
  return x * y;
};

1.2 アロー関数の構文

対照的に、アロー関数はよりコンパクトな構文を使用します。function キーワードを省略し、アロー (=>) を使用して引数リストと関数本体を分離します。

// アロー関数式 (Arrow Function Expression)
const add = (x, y) => {
  return x + y;
};

const multiply = (x, y) => x * y; // 暗黙的リターン (詳細は後述)

構文の主な違い:

  • アロー関数は function キーワードを使用しません。
  • アロー (=>) が引数リストと関数本体を分離します。
  • 関数本体が単一の式で構成されている場合、波括弧と return キーワードを省略できます(暗黙的リターン)。

2. パラメータ(引数)の処理

引数を処理する構文にも細かな違いがあります。

2.1 引数がない場合

関数が引数を受け取らない場合は、空の丸括弧を使用する必要があります。

// 従来の関数
function sayHello() {
  return "こんにちは!";
}

// アロー関数
const sayHello = () => "こんにちは!";

2.2 引数が1つの場合

関数が引数を1つだけ受け取る場合は、丸括弧を省略できます。

// 従来の関数
function square(x) {
  return x * x;
}

// アロー関数
const square = x => x * x;

2.3 複数の引数がある場合

関数が2つ以上の引数を受け取る場合は、それらを丸括弧で囲む必要があります。

// 従来の関数
function add(x, y) {
  return x + y;
}

// アロー関数
const add = (x, y) => x + y;

3. 暗黙的リターン (Implicit Returns)

アロー関数は「暗黙的リターン」と呼ばれる機能を提供しており、単一の式を返す関数の構文を簡略化できます。

3.1 暗黙的リターンが機能する仕組み

関数本体が単一の式で構成されている場合、波括弧と return キーワードを省略できます。その式の結果が自動的に返されます。

// 従来の関数
function double(x) {
  return x * 2;
}

// 暗黙的リターンを使用したアロー関数
const double = x => x * 2; // x * 2 が自動的に return される

3.2 暗黙的リターンを使用すべき時

暗黙的リターンは、単一の操作を実行して結果を返す、短くシンプルな関数に最適です。これによりコードが簡潔で読みやすくなります。

const isEven = x => x % 2 === 0; // 簡潔で読みやすい

3.3 暗黙的リターンを避けるべき時

関数本体に複数のステートメントが含まれる場合や、複雑なロジックが必要な場合は、波括弧と return キーワードを使用した明示的なリターンを使用するのが最適です。これにより、コードの可読性が高まり、理解しやすくなります。

const processValue = x => {
  const doubled = x * 2;
  const squared = doubled * doubled;
  return squared + 1; // 明確にするために明示的な return を使用
};

3.4 暗黙的リターンと明示的リターンの使い分け例:

// 数値の配列 (Array)
const numbers = [1, 2, 3, 4, 5];

// map と暗黙的リターンを使用して各数値の2乗を計算
const squaredNumbers = numbers.map(number => number * number);
console.log(squaredNumbers); // 出力: [1, 4, 9, 16, 25]

// map と明示的リターンを使用して、条件に応じて2乗または3乗を計算
const processedNumbers = numbers.map(number => {
  if (number % 2 === 0) {
    return number * number; // 偶数の場合は2乗
  } else {
    return number * number * number; // 奇数の場合は3乗
  }
});
console.log(processedNumbers); // 出力: [ 1, 4, 27, 16, 125 ]

4. this のバインド (this Binding)

アロー関数と従来の関数の間で最も重要な違いの一つは、this キーワードの取り扱いです。

4.1 従来の関数と this

従来の関数では、this の値は関数が どのように呼び出されたか によって決まります。それはグローバルオブジェクト(ブラウザでは window、Node.js では global)、関数を呼び出したオブジェクト(関数がオブジェクトのメソッドの場合)、あるいは callapplybind などのメソッドを使用して明示的に設定されたオブジェクトを指します。この動的な this バインドは、時として予期せぬ挙動や混乱を招く原因となります。

const myObject = {
  value: 42,
  getValue: function() {
    return this.value;
  }
};
console.log(myObject.getValue()); // 出力: 42

const getValue = myObject.getValue;
console.log(getValue()); // 出力: undefined (またはグローバルオブジェクトに 'value' があればその値)

4.2 アロー関数と this

一方で、アロー関数は独自の this バインドを持ちません。代わりに、周囲のスコープ(アロー関数が定義されたスコープ)から this の値を レキシカル (Lexical) に継承します。これは、アロー関数内部の this が常に外側の関数またはスコープの this を指すことを意味します。これは「レキシカル this」とも呼ばれます。

const myObject = {
  value: 42,
  getValue: () => {
    return this.value; // 'this' は周囲のスコープを指す
  }
};
// 非厳格モードでは undefined、厳格モードでは this が未定義ならエラーになる可能性があります。
console.log(myObject.getValue()); 

// より適切なユースケース
const myObjectCorrected = {
    value: 42,
    getValue: function() {
      const arrowFunction = () => this.value; // 'this' は getValue の 'this' を指す
      return arrowFunction();
    }
  };
  console.log(myObjectCorrected.getValue()); // 出力: 42

4.3 レキシカル this の影響

アロー関数のレキシカルな this バインドは、コールバックやネストされた関数など、外側のスコープの this の値を保持したい場合に特に有用です。これにより、.bind(this) を使用したり、var self = this; のように this を別の変数に保存したりする必要がなくなります。

function MyComponent() {
  this.value = 0;

  // 従来の関数では、'this' をバインドする必要がある
  setInterval(function() {
    this.value++;
    console.log("従来の関数: " + this.value); // 'this' はグローバルオブジェクト (window) を指してしまう
  }.bind(this), 1000);

  // アロー関数では、'this' はレキシカルにバインドされる
  setInterval(() => {
    this.value++;
    console.log("アロー関数: " + this.value); // 'this' は MyComponent インスタンスを指す
  }, 1000);
}
const myComponent = new MyComponent();

上記の例では、アロー関数が正しく MyComponent インスタンスを指しているのに対し、従来の関数は this がグローバルオブジェクトを指してしまいます。

4.4 アロー関数をメソッドとして使用すべきではない場合

アロー関数は this をレキシカルにバインドするため、this がオブジェクト自体を指す必要があるオブジェクトのメソッド定義には適していません。このような場合は従来の関数を使用してください。

const person = {
  name: "アリス",
  // greet: () => { // エラー: 'this' は 'person' を指さない
  greet: function() { // 正解: 'this' は 'person' を指す
    console.log("こんにちは、私の名前は " + this.name);
  }
};
person.greet(); // 出力: こんにちは、私の名前は アリス

const personArrow = {
    name: "アリス",
    greet: () => { // 失敗例: 'this' は 'personArrow' を指さない
      console.log("こんにちは、私の名前は " + this.name);
    }
  };
// 出力: こんにちは、私の名前は undefined (外部スコープに 'name' が定義されていない場合)
personArrow.greet();

5. arguments オブジェクト

もう一つの重要な違いは、アロー関数における arguments オブジェクトの取り扱いです。

5.1 従来の関数と arguments

従来の関数は arguments オブジェクトにアクセスできます。これは、関数に渡されたすべての引数を含む配列風オブジェクトであり、引数リストに明示的に定義されているかどうかにかかわらずすべての引数を保持します。

function logArguments() {
  console.log(arguments);
  for (let i = 0; i < arguments.length; i++) {
    console.log("引数 " + i + ": " + arguments[i]);
  }
}
logArguments(1, 2, 3, "hello"); // 出力: { '0': 1, '1': 2, '2': 3, '3': 'hello' }

5.2 アロー関数と arguments

アロー関数は独自の arguments オブジェクトを持ちません。アロー関数内部で arguments にアクセスしようとすると、それは周囲の関数(もしあれば)の arguments オブジェクトとして解決されます。

function outerFunction() {
  const logArguments = () => {
    console.log(arguments); // outerFunction の 'arguments' を参照する
  };
  logArguments(4, 5, 6);
}
outerFunction(1, 2, 3); // 出力: { '0': 1, '1': 2, '2': 3 }

const logArgumentsArrow = () => {
    console.log(arguments); // エラー: arguments は未定義です
  };

5.3 代わりにレストパラメータ (Rest Parameters) を使用する

アロー関数に渡された引数にアクセスする必要がある場合は、レストパラメータ(ES6で導入)を使用すべきです。レストパラメータを使用すると、不特定多数の引数を配列として表現できます。

const logArguments = (...args) => {
  console.log(args);
  for (let i = 0; i < args.length; i++) {
    console.log("引数 " + i + ": " + args[i]);
  }
};
logArguments(1, 2, 3, "hello"); // 出力: [ 1, 2, 3, 'hello' ]

レストパラメータは本物の配列であるため、すべての配列メソッドを使用でき、arguments オブジェクトよりも柔軟で扱いやすいです。

6. new キーワード

従来の関数は、new キーワードと一緒に コンストラクタ (Constructor) として使用し、新しいオブジェクトを作成できます。

6.1 コンストラクタとしての従来の関数

function Person(name) {
  this.name = name;
}
const person1 = new Person("アリス");
console.log(person1.name); // 出力: アリス

6.2 アロー関数はコンストラクタとして使用できない

アロー関数はコンストラクタとして使用できません。アロー関数を new で呼び出そうとすると、TypeError が発生します。

const Person = (name) => {
  this.name = name;
};
// const person1 = new Person("アリス"); // TypeError: Person is not a constructor

これは、アロー関数が軽量で式指向の関数として設計されており、コンストラクタとして機能するために必要な内部メソッドを持っていないためです。

7. prototype プロパティ

従来の関数は、JavaScript における継承を実現するために使用される prototype プロパティを持っています。

7.1 従来の関数と prototype

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  console.log("こんにちは、私の名前は " + this.name);
};
const person1 = new Person("アリス");
person1.greet(); // 出力: こんにちは、私の名前は アリス

7.2 アロー関数は prototype を持たない

アロー関数は prototype プロパティを持ちません。これは、コンストラクタとして使用できないという事実と整合しています。

const Person = (name) => {
  this.name = name;
};
console.log(Person.prototype); // 出力: undefined

8. まとめ

特性従来の関数アロー関数
シンタックスfunction キーワード=> アロー記号
this バインド動的 (Dynamic)レキシカル (Lexical)
argumentsarguments オブジェクトありなし (レストパラメータを使用)
コンストラクタnew を使用可能new は使用不可
prototypeprototype プロパティありなし
暗黙的リターンなしあり