this...).call()、.apply()、.bind().call().apply().bind()この章では、JavaScriptで呼び出すことができる値(関数、メソッド、クラス)について見ていきます。
JavaScriptには、2つのカテゴリの関数があります。
通常の関数は、いくつかの役割を果たすことができます。
特殊関数は、それらの役割の1つしか果たすことができません。例えば
特殊関数は、ECMAScript 6で言語に追加されました。
これらのものがすべて何を意味するのかを理解するために読み進めてください。
次のコードは、(ほぼ)同じことを行う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の宣言のような変数宣言は、早期にアクティブ化されません。
これまで、匿名関数式のみを見てきました。これらは名前を持っていません。
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つは、それらの名前がエラースタックトレースに表示されることです。
関数定義は、関数を作成する構文です。
関数宣言は、常に通常の関数を生成します。関数式は、通常の関数または特殊関数を生成します。
関数宣言はJavaScriptで依然として一般的ですが、関数式は、最新のコードではほとんど常にアロー関数です。
次の例を使用して、関数宣言の構成要素を調べてみましょう。ほとんどの用語は、関数式にも適用されます。
function add(x, y) {
return x + y;
}addは、関数宣言の名前です。add(x, y)は、関数宣言のヘッダーです。xとyはパラメーターです。{と})とその間のすべてが、関数宣言の本体です。returnステートメントは、関数から明示的に値を返します。JavaScriptでは、常に配列リテラルで末尾のコンマが許可され、無視されてきました。ES5以降、オブジェクトリテラルでも許可されています。ES2017以降、パラメーターリスト(宣言と呼び出し)に末尾のコンマを追加できます。
// Declaration
function retrieveData(
contentText,
keyword,
{unique, ignoreCase, pageSize}, // trailing comma
) {
// ···
}
// Invocation
retrieveData(
'',
null,
{ignoreCase: true, pageSize: 10}, // trailing comma
);前のセクションの次の関数宣言を考えてください。
function add(x, y) {
return x + y;
}この関数宣言は、名前がaddである通常の関数を作成します。通常の関数として、add()は3つの役割を果たすことができます。
assert.equal(add(2, 1), 3);メソッド: プロパティに格納され、メソッド呼び出しを介して呼び出されます。
const obj = { addAsMethod: add };
assert.equal(obj.addAsMethod(2, 4), 6); // (A)A行では、objはメソッド呼び出しのレシーバーと呼ばれます。
const inst = new add();
assert.equal(inst instanceof add, true);ちなみに、コンストラクター関数(クラスを含む)の名前は通常、大文字で始まります。
構文、エンティティ、および役割の概念の区別は微妙であり、多くの場合重要ではありません。しかし、私はあなたのために目を研ぎ澄ませたいと思います。
他の多くのプログラミング言語には、役割実際の関数を果たす単一のエンティティしかありません。次に、役割とエンティティの両方に名前関数を使用できます。
特殊関数は、通常の関数の単一目的バージョンです。それらのそれぞれが単一の役割に特化しています。
アロー関数の目的は、実際の関数であることです。
const arrow = () => {
return 123;
};
assert.equal(arrow(), 123);メソッドの目的は、メソッドであることです。
const obj = {
myMethod() {
return 'abc';
}
};
assert.equal(obj.myMethod(), 'abc');クラスの目的は、コンストラクター関数であることです。
class MyClass {
/* ··· */
}
const inst = new MyClass();より優れた構文とは別に、各種類の特殊関数は、新しい機能もサポートしており、通常の関数よりもジョブに適しています。
表16には、通常関数と特殊関数の機能がリストされています。
| 関数呼び出し | メソッド呼び出し | コンストラクター呼び出し | |
|---|---|---|---|
| 通常の関数 | (this === undefined) |
✔ |
✔ |
| アロー関数 | ✔ |
(レキシカル this) |
✘ |
| メソッド | (this === undefined) |
✔ |
✘ |
| クラス | ✘ |
✘ |
✔ |
アロー関数、メソッド、クラスは、依然として関数として分類されることに注意することが重要です。
> (() => {}) instanceof Function
true
> ({ method() {} }.method) instanceof Function
true
> (class SomeClass {}) instanceof Function
trueアロー関数は、次の2つの理由でJavaScriptに追加されました。
thisを介してメソッド呼び出しを受信したオブジェクトを参照できます。アロー関数は、周囲のメソッドのthisにアクセスできます。通常の関数はアクセスできません(独自のthisを持っているため)。最初にアロー関数の構文を調べ、次にさまざまな関数でのthisの仕組みを調べます。
匿名関数式の構文を確認してみましょう。
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 });アロー関数の式本体をオブジェクトリテラルにする場合、リテラルを括弧で囲む必要があります。
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に本体が文(ブロック)ではなく式(オブジェクトリテラル)であることを伝えます。
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つの方法を観察できます。
動的な this:A行では、通常の関数から .someMethod() の this にアクセスしようとしています。そこでは、関数の独自の this によってシャドウされており、undefined になっています(関数呼び出しによって埋め込まれます)。通常の関数は(動的な)関数またはメソッド呼び出しを介して this を受け取るため、それらの this は動的と呼ばれます。
静的な this:B行では、再び .someMethod() の this にアクセスしようとしています。今回は、アロー関数に独自の this がないため、成功します。this は、他の変数と同じように静的に解決されます。そのため、アロー関数の this は静的と呼ばれます。
通常、通常の関数よりも特殊化された関数、特にクラスとメソッドを優先する必要があります。
実際の関数に関しては、アロー関数と通常の関数のどちらを選択するかはそれほど明確ではありません。
匿名インライン関数式の場合、アロー関数はコンパクトな構文と、暗黙的なパラメータとして this を持たないため、明確な勝者です。
const twiceOrdinary = [1, 2, 3].map(function (x) {return x * 2});
const twiceArrow = [1, 2, 3].map(x => x * 2);スタンドアロンの名前付き関数宣言の場合、アロー関数は静的な this の恩恵を受け続けます。ただし、関数宣言(通常の関数を生成する)は、優れた構文を持ち、早期アクティブ化も時々役立ちます(§11.8「宣言:スコープとアクティブ化」を参照)。this が通常の関数の本体に現れない場合、実際の関数として使用してもデメリットはありません。静的チェックツールESLintは、組み込みルールを使用して、開発中にこれを間違った場合に警告できます。
function timesOrdinary(x, y) {
return x * y;
}
const timesArrow = (x, y) => {
return x * y;
}; このセクションは今後の内容を参照しています
このセクションは主に、現在および今後の章のリファレンスとして役立ちます。すべてを理解していなくても心配する必要はありません。
これまで見てきたすべての(実際の)関数とメソッドは、
後の章では、他のプログラミングモードについて説明します。
これらのモードを組み合わせることができます。たとえば、同期イテラブルと非同期イテラブルがあります。
いくつかの新しい種類の関数とメソッドは、モードの組み合わせの一部を支援します。
これにより、関数とメソッドの4種類(2×2)が残ります。
表 17 は、これら4種類の関数とメソッドを作成するための構文の概要を示しています。
| 結果 | # | ||
|---|---|---|---|
| 同期関数 | 同期メソッド | ||
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* () {} |
(このセクションで言及されていることはすべて、関数とメソッドの両方に適用されます。)
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);このセクションでは、関数のみに言及していますが、すべてメソッドにも適用されます。
パラメータという用語と引数という用語は、基本的に同じ意味です。必要に応じて、次の区別を行うことができます。
パラメータは、関数定義の一部です。仮パラメータまたは仮引数とも呼ばれます。
引数は、関数呼び出しの一部です。実パラメータまたは実引数とも呼ばれます。
コールバックまたはコールバック関数は、関数またはメソッド呼び出しの引数である関数です。
以下は、コールバックの例です。
const myArray = ['a', 'b'];
const callback = (x) => console.log(x);
myArray.forEach(callback);
// Output:
// 'a'
// 'b'JavaScriptは、関数呼び出しが関数定義で想定されているものとは異なる数の引数を提供した場合に文句を言いません。
undefined に設定されます。たとえば
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]);パラメータのデフォルト値は、パラメータが提供されていない場合に使用する値を指定します。たとえば
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]);レストパラメータは、識別子の前に3つのドット(...)を付けることで宣言されます。関数またはメソッドの呼び出し中に、残りのすべての引数を含む配列を受け取ります。最後に余分な引数がない場合は、空の配列になります。たとえば
function f(x, ...y) {
return [x, y];
}
assert.deepEqual(
f('a', 'b', 'c'), ['a', ['b', 'c']]
);
assert.deepEqual(
f(), [undefined, []]
);レストパラメータの使用方法には、2つの制限があります。
関数定義ごとに複数のレストパラメータを使用することはできません。
assert.throws(
() => eval('function f(...x, ...y) {}'),
/^SyntaxError: Rest parameter must be last formal parameter$/
);レストパラメータは、常に最後に来る必要があります。その結果、次のように最後のパラメータにアクセスすることはできません。
assert.throws(
() => eval('function f(...restParams, lastParam) {}'),
/^SyntaxError: Rest parameter must be last formal parameter$/
);レストパラメータを使用して、特定の数の引数を強制できます。たとえば、次の関数を考えてみましょう。
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 の要素にアクセスします。
誰かが関数を呼び出すと、呼び出し元によって提供された引数は、呼び出し先によって受信されたパラメータに割り当てられます。マッピングを実行する2つの一般的な方法は、次のとおりです。
位置パラメータ:引数は、同じ位置にある場合にパラメータに割り当てられます。位置パラメータのみを使用した関数呼び出しは、次のようになります。
selectEntries(3, 20, 2)名前付きパラメータ:引数は、同じ名前を持つ場合にパラメータに割り当てられます。JavaScriptには名前付きパラメータはありませんが、シミュレートできます。たとえば、これは(シミュレートされた)名前付き引数のみを使用した関数呼び出しです。
selectEntries({start: 3, end: 20, step: 2})名前付きパラメータにはいくつかの利点があります。
各引数に説明的なラベルがあるため、より自己説明的なコードになります。selectEntries() の2つのバージョンを比較するだけで、2番目のバージョンでは何が起こっているかをはるかに理解しやすくなります。
引数の順序は重要ではありません(名前が正しい限り)。
複数のオプションパラメータの処理がより便利になります。呼び出し元は、すべてのオプションパラメータの任意のサブセットを簡単に提供でき、省略するパラメータを認識する必要はありません(位置パラメータを使用すると、先行するオプションパラメータを undefined で埋める必要があります)。
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 });...)関数呼び出しの引数の前に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'展開とレストパラメータは同じ構文(...)を使用しますが、反対の目的を果たします。
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)
11Array.prototype.push() への展開同様に、配列メソッド .push() は、0個以上のパラメータを配列の末尾に破壊的に追加します。JavaScriptには、ある配列を別の配列に破壊的に追加するメソッドはありません。ここでも、展開によって救われます。
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];
arr1.push(...arr2);
assert.deepEqual(arr1, ['a', 'b', 'c', 'd']); 演習: パラメータの扱い
exercises/callables/positional_parameters_test.mjsexercises/callables/named_parameters_test.mjs.call(), .apply(), .bind()関数はオブジェクトであり、メソッドを持っています。このセクションでは、それらのメソッドのうち3つ、.call()、.apply()、そして .bind() について見ていきます。
.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']);これまで見てきたように、普通の関数を関数呼び出しする場合、その this は undefined になります。
assert.deepEqual(
func('a', 'b'),
[undefined, 'a', 'b']);したがって、上記の関数呼び出しは以下と同等です
assert.deepEqual(
func.call(undefined, 'a', 'b'),
[undefined, 'a', 'b']);アロー関数では、.call() (または他の手段) を介して提供される this の値は無視されます。
.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']);.bind().bind() は関数オブジェクトの別のメソッドです。このメソッドは次のように呼び出されます
const boundFunc = someFunc.bind(thisValue, arg1, arg2);.bind() は新しい関数 boundFunc() を返します。その関数を呼び出すと、someFunc() が this が thisValue に設定され、かつこれらのパラメータ (arg1, arg2) の後に boundFunc() のパラメータが渡されて呼び出されます。
つまり、次の2つの関数呼び出しは同等です
boundFunc('a', 'b')
someFunc.call(thisValue, arg1, arg2, 'a', 'b').bind() の代替this とパラメータを事前に設定するもう1つの方法は、アロー関数を使用することです
const boundFunc2 = (...args) =>
someFunc.call(thisValue, arg1, arg2, ...args);.bind() の実装前のセクションを考慮すると、.bind() は次のように実際の関数として実装できます
function bind(func, thisValue, ...boundArgs) {
return (...args) =>
func.call(thisValue, ...boundArgs, ...args);
}実関数に .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); クイズ
クイズアプリ を参照してください。