JavaScript (気短なプログラマー向け) (ES2022 版)
この書籍をサポートしてください: 購入 または 寄付
(広告、ブロックしないでください。)

25 呼び出し可能な値



この章では、JavaScriptで呼び出すことができる値(関数、メソッド、クラス)について見ていきます。

25.1 関数の種類

JavaScriptには、2つのカテゴリの関数があります。

これらのものがすべて何を意味するのかを理解するために読み進めてください。

25.2 通常の関数

次のコードは、(ほぼ)同じことを行う2つの方法、つまり通常の関数を作成する方法を示しています。

// Function declaration (a statement)
function ordinary1(a, b, c) {
  // ···
}

// const plus anonymous (nameless) function expression
const ordinary2 = function (a, b, c) {
  // ···
};

スコープ内で、関数宣言は早期にアクティブ化され(§11.8「宣言: スコープとアクティベーション」を参照)、宣言される前に呼び出すことができます。それは時々役立ちます。

ordinary2の宣言のような変数宣言は、早期にアクティブ化されません。

25.2.1 名前付き関数式 (上級)

これまで、匿名関数式のみを見てきました。これらは名前を持っていません。

const anonFuncExpr = function (a, b, c) {
  // ···
};

しかし、名前付き関数式もあります。

const namedFuncExpr = function myName(a, b, c) {
  // `myName` is only accessible in here
};

myNameは、関数の本体内でのみアクセス可能です。関数はそれを使用して(自己再帰などのために)自分自身を参照できます。どの変数に割り当てられているかに関係なく。

const func = function funcExpr() { return funcExpr };
assert.equal(func(), func);

// The name `funcExpr` only exists inside the function body:
assert.throws(() => funcExpr(), ReferenceError);

変数に割り当てられていない場合でも、名前付き関数式には名前があります(A行)。

function getNameOfCallback(callback) {
  return callback.name;
}

assert.equal(
  getNameOfCallback(function () {}), ''); // anonymous

assert.equal(
  getNameOfCallback(function named() {}), 'named'); // (A)

関数宣言または変数宣言を介して作成された関数は常に名前を持っていることに注意してください。

function funcDecl() {}
assert.equal(
  getNameOfCallback(funcDecl), 'funcDecl');

const funcExpr = function () {};
assert.equal(
  getNameOfCallback(funcExpr), 'funcExpr');

関数に名前があることの利点の1つは、それらの名前がエラースタックトレースに表示されることです。

25.2.2 用語: 関数定義と関数式

関数定義は、関数を作成する構文です。

関数宣言は、常に通常の関数を生成します。関数式は、通常の関数または特殊関数を生成します。

関数宣言はJavaScriptで依然として一般的ですが、関数式は、最新のコードではほとんど常にアロー関数です。

25.2.3 関数宣言の構成要素

次の例を使用して、関数宣言の構成要素を調べてみましょう。ほとんどの用語は、関数式にも適用されます。

function add(x, y) {
  return x + y;
}
25.2.3.1 パラメーターリストの末尾のコンマ

JavaScriptでは、常に配列リテラルで末尾のコンマが許可され、無視されてきました。ES5以降、オブジェクトリテラルでも許可されています。ES2017以降、パラメーターリスト(宣言と呼び出し)に末尾のコンマを追加できます。

// Declaration
function retrieveData(
  contentText,
  keyword,
  {unique, ignoreCase, pageSize}, // trailing comma
) {
  // ···
}

// Invocation
retrieveData(
  '',
  null,
  {ignoreCase: true, pageSize: 10}, // trailing comma
);

25.2.4 通常の関数が果たす役割

前のセクションの次の関数宣言を考えてください。

function add(x, y) {
  return x + y;
}

この関数宣言は、名前がaddである通常の関数を作成します。通常の関数として、add()は3つの役割を果たすことができます。

25.2.5 用語: エンティティ vs. 構文 vs. 役割 (上級)

構文エンティティ、および役割の概念の区別は微妙であり、多くの場合重要ではありません。しかし、私はあなたのために目を研ぎ澄ませたいと思います。

他の多くのプログラミング言語には、役割実際の関数を果たす単一のエンティティしかありません。次に、役割とエンティティの両方に名前関数を使用できます。

25.3 特殊関数

特殊関数は、通常の関数の単一目的バージョンです。それらのそれぞれが単一の役割に特化しています。

より優れた構文とは別に、各種類の特殊関数は、新しい機能もサポートしており、通常の関数よりもジョブに適しています。

16には、通常関数と特殊関数の機能がリストされています。

表16: 4種類の関数の機能。セル値が括弧で囲まれている場合は、何らかの制限があることを意味します。特殊変数thisについては、§25.3.3「メソッド、通常の関数、アロー関数における特殊変数thisで説明します。
関数呼び出し メソッド呼び出し コンストラクター呼び出し
通常の関数 (this === undefined)
アロー関数 (レキシカル this)
メソッド (this === undefined)
クラス

25.3.1 特殊関数も依然として関数である

アロー関数、メソッド、クラスは、依然として関数として分類されることに注意することが重要です。

> (() => {}) instanceof Function
true
> ({ method() {} }.method) instanceof Function
true
> (class SomeClass {}) instanceof Function
true

25.3.2 アロー関数

アロー関数は、次の2つの理由でJavaScriptに追加されました。

  1. 関数を作成するためのより簡潔な方法を提供するため。
  2. メソッド内の実際の関数としてより適切に機能します。メソッドは、特殊変数thisを介してメソッド呼び出しを受信したオブジェクトを参照できます。アロー関数は、周囲のメソッドのthisにアクセスできます。通常の関数はアクセスできません(独自のthisを持っているため)。

最初にアロー関数の構文を調べ、次にさまざまな関数でのthisの仕組みを調べます。

25.3.2.1 アロー関数の構文

匿名関数式の構文を確認してみましょう。

const f = function (x, y, z) { return 123 };

(ほぼ)同等のアロー関数は次のようになります。アロー関数は式です。

const f = (x, y, z) => { return 123 };

ここで、アロー関数の本体はブロックです。しかし、式にすることもできます。次のアロー関数は、前の関数とまったく同じように機能します。

const f = (x, y, z) => 123;

アロー関数が単一のパラメータを持ち、そのパラメータが識別子である場合(分割代入パターンではない場合)、パラメータを囲む括弧を省略できます。

const id = x => x;

これは、アロー関数を他の関数やメソッドへのパラメータとして渡す場合に便利です。

> [1,2,3].map(x => x+1)
[ 2, 3, 4 ]

前の例は、アロー関数の利点の一つである簡潔さを示しています。関数式で同じタスクを実行する場合、コードはより冗長になります。

[1,2,3].map(function (x) { return x+1 });
25.3.2.2 構文上の落とし穴:アロー関数からオブジェクトリテラルを返す

アロー関数の式本体をオブジェクトリテラルにする場合、リテラルを括弧で囲む必要があります。

const func1 = () => ({a: 1});
assert.deepEqual(func1(), { a: 1 });

そうしないと、JavaScriptはアロー関数がブロック本体を持つ(何も返さない)と解釈します。

const func2 = () => {a: 1};
assert.deepEqual(func2(), undefined);

{a: 1} は、ラベル a: と式文 1 を持つブロックとして解釈されます。明示的な return 文がない場合、ブロック本体は undefined を返します。

この落とし穴は、構文の曖昧さによって引き起こされます。オブジェクトリテラルとコードブロックは同じ構文を持ちます。括弧を使用して、JavaScriptに本体が文(ブロック)ではなく式(オブジェクトリテラル)であることを伝えます。

25.3.3 メソッド、通常の関数、およびアロー関数における特殊変数 this

  特殊変数 this はオブジェクト指向の機能です

ここでは、アロー関数が通常の関数よりも優れた実際の関数である理由を理解するために、特殊変数 this を簡単に見ていきます。

ただし、この機能はオブジェクト指向プログラミングでのみ重要であり、§28.5「メソッドと特殊変数 thisでより詳しく説明します。したがって、まだ完全に理解していなくても心配する必要はありません。

メソッド内では、特殊変数 this を使用して、メソッド呼び出しを受信したオブジェクトであるレシーバーにアクセスできます。

const obj = {
  myMethod() {
    assert.equal(this, obj);
  }
};
obj.myMethod();

通常の関数はメソッドにすることができ、したがって暗黙的なパラメータ this も持ちます。

const obj = {
  myMethod: function () {
    assert.equal(this, obj);
  }
};
obj.myMethod();

通常の関数を実際の関数として使用する場合でも、this は暗黙的なパラメータです。その場合、値は undefined になります(厳格モードがアクティブな場合。これはほとんど常にアクティブです)。

function ordinaryFunc() {
  assert.equal(this, undefined);
}
ordinaryFunc();

つまり、実際の関数として使用される通常の関数は、周囲のメソッドの this にアクセスできません(A行)。対照的に、アロー関数には暗黙的なパラメータとして this がありません。他の変数と同じように扱い、したがって周囲のメソッドの this にアクセスできます(B行)。

const jill = {
  name: 'Jill',
  someMethod() {
    function ordinaryFunc() {
      assert.throws(
        () => this.name, // (A)
        /^TypeError: Cannot read properties of undefined \(reading 'name'\)$/);
    }
    ordinaryFunc();

    const arrowFunc = () => {
      assert.equal(this.name, 'Jill'); // (B)
    };
    arrowFunc();
  },
};
jill.someMethod();

このコードでは、this を処理する2つの方法を観察できます。

25.3.4 推奨事項:通常の関数よりも特殊化された関数を優先する

通常、通常の関数よりも特殊化された関数、特にクラスとメソッドを優先する必要があります。

実際の関数に関しては、アロー関数と通常の関数のどちらを選択するかはそれほど明確ではありません。

25.4 まとめ:呼び出し可能な値の種類

  このセクションは今後の内容を参照しています

このセクションは主に、現在および今後の章のリファレンスとして役立ちます。すべてを理解していなくても心配する必要はありません。

これまで見てきたすべての(実際の)関数とメソッドは、

後の章では、他のプログラミングモードについて説明します。

これらのモードを組み合わせることができます。たとえば、同期イテラブルと非同期イテラブルがあります。

いくつかの新しい種類の関数とメソッドは、モードの組み合わせの一部を支援します。

これにより、関数とメソッドの4種類(2×2)が残ります。

表 17 は、これら4種類の関数とメソッドを作成するための構文の概要を示しています。

表17:関数とメソッドを作成するための構文。最後の列は、エンティティによって生成される値の数を指定します。
結果 #
同期関数 同期メソッド
function f() {} { m() {} } 1
f = function () {}
f = () => {}
同期ジェネレータ関数 同期ジェネレータメソッド
function* f() {} { * m() {} } イテラブル 0+
f = function* () {}
非同期関数 非同期メソッド
async function f() {} { async m() {} } Promise 1
f = async function () {}
f = async () => {}
非同期ジェネレータ関数 非同期ジェネレータメソッド
async function* f() {} { async * m() {} } 非同期イテラブル 0+
f = async function* () {}

25.5 関数とメソッドからの値の返し

(このセクションで言及されていることはすべて、関数とメソッドの両方に適用されます。)

return 文は、関数から明示的に値を返します。

function func() {
  return 123;
}
assert.equal(func(), 123);

別の例

function boolToYesNo(bool) {
  if (bool) {
    return 'Yes';
  } else {
    return 'No';
  }
}
assert.equal(boolToYesNo(true), 'Yes');
assert.equal(boolToYesNo(false), 'No');

関数の最後に何も明示的に返さなかった場合、JavaScriptは undefined を返します。

function noReturn() {
  // No explicit return
}
assert.equal(noReturn(), undefined);

25.6 パラメータ処理

このセクションでは、関数のみに言及していますが、すべてメソッドにも適用されます。

25.6.1 用語:パラメータ vs. 引数

パラメータという用語と引数という用語は、基本的に同じ意味です。必要に応じて、次の区別を行うことができます。

25.6.2 用語:コールバック

コールバックまたはコールバック関数は、関数またはメソッド呼び出しの引数である関数です。

以下は、コールバックの例です。

const myArray = ['a', 'b'];
const callback = (x) => console.log(x);
myArray.forEach(callback);

// Output:
// 'a'
// 'b'

25.6.3 多すぎるまたは不足している引数

JavaScriptは、関数呼び出しが関数定義で想定されているものとは異なる数の引数を提供した場合に文句を言いません。

たとえば

function foo(x, y) {
  return [x, y];
}

// Too many arguments:
assert.deepEqual(foo('a', 'b', 'c'), ['a', 'b']);

// The expected number of arguments:
assert.deepEqual(foo('a', 'b'), ['a', 'b']);

// Not enough arguments:
assert.deepEqual(foo('a'), ['a', undefined]);

25.6.4 パラメータのデフォルト値

パラメータのデフォルト値は、パラメータが提供されていない場合に使用する値を指定します。たとえば

function f(x, y=0) {
  return [x, y];
}

assert.deepEqual(f(1), [1, 0]);
assert.deepEqual(f(), [undefined, 0]);

undefined もデフォルト値をトリガーします。

assert.deepEqual(
  f(undefined, undefined),
  [undefined, 0]);

25.6.5 レストパラメータ

レストパラメータは、識別子の前に3つのドット(...)を付けることで宣言されます。関数またはメソッドの呼び出し中に、残りのすべての引数を含む配列を受け取ります。最後に余分な引数がない場合は、空の配列になります。たとえば

function f(x, ...y) {
  return [x, y];
}
assert.deepEqual(
  f('a', 'b', 'c'), ['a', ['b', 'c']]
);
assert.deepEqual(
  f(), [undefined, []]
);

レストパラメータの使用方法には、2つの制限があります。

25.6.5.1 レストパラメータを介して特定の数の引数を強制する

レストパラメータを使用して、特定の数の引数を強制できます。たとえば、次の関数を考えてみましょう。

function createPoint(x, y) {
  return {x, y};
    // same as {x: x, y: y}
}

これは、呼び出し元に常に2つの引数を提供するように強制する方法です。

function createPoint(...args) {
  if (args.length !== 2) {
    throw new Error('Please provide exactly 2 arguments!');
  }
  const [x, y] = args; // (A)
  return {x, y};
}

A行では、分割代入を介して args の要素にアクセスします。

25.6.6 名前付きパラメータ

誰かが関数を呼び出すと、呼び出し元によって提供された引数は、呼び出し先によって受信されたパラメータに割り当てられます。マッピングを実行する2つの一般的な方法は、次のとおりです。

  1. 位置パラメータ:引数は、同じ位置にある場合にパラメータに割り当てられます。位置パラメータのみを使用した関数呼び出しは、次のようになります。

    selectEntries(3, 20, 2)
  2. 名前付きパラメータ:引数は、同じ名前を持つ場合にパラメータに割り当てられます。JavaScriptには名前付きパラメータはありませんが、シミュレートできます。たとえば、これは(シミュレートされた)名前付き引数のみを使用した関数呼び出しです。

    selectEntries({start: 3, end: 20, step: 2})

名前付きパラメータにはいくつかの利点があります。

25.6.7 名前付きパラメータのシミュレーション

JavaScriptには、実際には名前付きパラメータはありません。それらをシミュレートする公式な方法は、オブジェクトリテラルを使用することです。

function selectEntries({start=0, end=-1, step=1}) {
  return {start, end, step};
}

この関数は、分割代入を使用して、単一のパラメータのプロパティにアクセスします。使用するパターンは、次のパターンの省略形です。

{start: start=0, end: end=-1, step: step=1}

この分割代入パターンは、空のオブジェクトリテラルで機能します。

> selectEntries({})
{ start: 0, end: -1, step: 1 }

ただし、パラメータなしで関数を呼び出す場合は機能しません。

> selectEntries()
TypeError: Cannot read properties of undefined (reading 'start')

パターン全体のデフォルト値を指定することで、これを修正できます。このデフォルト値は、より単純なパラメータ定義のデフォルト値と同じように機能します。パラメータがない場合は、デフォルトが使用されます。

function selectEntries({start=0, end=-1, step=1} = {}) {
  return {start, end, step};
}
assert.deepEqual(
  selectEntries(),
  { start: 0, end: -1, step: 1 });

25.6.8 関数呼び出しへの展開(...

関数呼び出しの引数の前に3つのドット(...)を付けると、それを展開します。つまり、引数はイテラブルオブジェクトである必要があり、反復処理された値はすべて引数になります。言い換えれば、単一の引数が複数の引数に展開されます。たとえば

function func(x, y) {
  console.log(x);
  console.log(y);
}
const someIterable = ['a', 'b'];
func(...someIterable);
  // same as func('a', 'b')

// Output:
// 'a'
// 'b'

展開とレストパラメータは同じ構文(...)を使用しますが、反対の目的を果たします。

25.6.8.1 例:Math.max() への展開

Math.max() は、0個以上の引数の中で最大値を返します。残念ながら、配列には使用できませんが、展開することで解決できます。

> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
> Math.max(-1, ...[-5, 11], 3)
11
25.6.8.2 例:Array.prototype.push() への展開

同様に、配列メソッド .push() は、0個以上のパラメータを配列の末尾に破壊的に追加します。JavaScriptには、ある配列を別の配列に破壊的に追加するメソッドはありません。ここでも、展開によって救われます。

const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];

arr1.push(...arr2);
assert.deepEqual(arr1, ['a', 'b', 'c', 'd']);

  演習: パラメータの扱い

25.7 関数のメソッド: .call(), .apply(), .bind()

関数はオブジェクトであり、メソッドを持っています。このセクションでは、それらのメソッドのうち3つ、.call().apply()、そして .bind() について見ていきます。

25.7.1 関数メソッド .call()

各関数 someFunc は以下のメソッドを持っています

someFunc.call(thisValue, arg1, arg2, arg3);

このメソッド呼び出しは、おおまかに次の関数呼び出しと同等です

someFunc(arg1, arg2, arg3);

しかし、.call() を使うと、暗黙のパラメータ this の値を指定することもできます。言い換えれば、.call() は暗黙のパラメータ this を明示的にします。

次のコードは、.call() の使用法を示しています

function func(x, y) {
  return [this, x, y];
}

assert.deepEqual(
  func.call('hello', 'a', 'b'),
  ['hello', 'a', 'b']);

これまで見てきたように、普通の関数を関数呼び出しする場合、その thisundefined になります。

assert.deepEqual(
  func('a', 'b'),
  [undefined, 'a', 'b']);

したがって、上記の関数呼び出しは以下と同等です

assert.deepEqual(
  func.call(undefined, 'a', 'b'),
  [undefined, 'a', 'b']);

アロー関数では、.call() (または他の手段) を介して提供される this の値は無視されます。

25.7.2 関数メソッド .apply()

各関数 someFunc は以下のメソッドを持っています

someFunc.apply(thisValue, [arg1, arg2, arg3]);

このメソッド呼び出しは、おおまかに次の関数呼び出しと同等です (これは スプレッド を使用しています)

someFunc(...[arg1, arg2, arg3]);

しかし、.apply() を使うと、暗黙のパラメータ this の値を指定することもできます。

次のコードは、.apply() の使用法を示しています

function func(x, y) {
  return [this, x, y];
}

const args = ['a', 'b'];
assert.deepEqual(
  func.apply('hello', args),
  ['hello', 'a', 'b']);

25.7.3 関数メソッド .bind()

.bind() は関数オブジェクトの別のメソッドです。このメソッドは次のように呼び出されます

const boundFunc = someFunc.bind(thisValue, arg1, arg2);

.bind() は新しい関数 boundFunc() を返します。その関数を呼び出すと、someFunc()thisthisValue に設定され、かつこれらのパラメータ (arg1, arg2) の後に boundFunc() のパラメータが渡されて呼び出されます。

つまり、次の2つの関数呼び出しは同等です

boundFunc('a', 'b')
someFunc.call(thisValue, arg1, arg2, 'a', 'b')
25.7.3.1 .bind() の代替

this とパラメータを事前に設定するもう1つの方法は、アロー関数を使用することです

const boundFunc2 = (...args) =>
  someFunc.call(thisValue, arg1, arg2, ...args);
25.7.3.2 .bind() の実装

前のセクションを考慮すると、.bind() は次のように実際の関数として実装できます

function bind(func, thisValue, ...boundArgs) {
  return (...args) =>
    func.call(thisValue, ...boundArgs, ...args);
}
25.7.3.3 例: 実関数をバインドする

実関数に .bind() を使用するのは、this の値を指定する必要があるため、やや直感的ではありません。関数呼び出し中は undefined であるため、通常は undefined または null に設定されます。

次の例では、add() の最初のパラメータを 8 にバインドすることで、1つのパラメータを持つ関数 add8() を作成します。

function add(x, y) {
  return x + y;
}

const add8 = add.bind(undefined, 8);
assert.equal(add8(1), 9);

  クイズ

クイズアプリ を参照してください。