関数とは、一度定義された後に何度も呼び出される、パラメトリックなコードのブロックです。 JavaScriptでは、関数は多くのコンポーネントによって構成され、影響を受けます。
- 関数本体を構成するJavaScriptのコード
- パラメータのリスト
- 辞書的なスコープからアクセス可能な変数
- 戻り値
- 関数が呼び出されたときのコンテキスト
this
- 名前付きまたは匿名の関数 。
この記事では、JavaScriptの関数を宣言するための6つのアプローチを紹介します。 この記事では、JavaScriptの関数を宣言するための6つのアプローチ、つまり、構文、例、よくある落とし穴について学びます。 さらに、特定の状況下で特定の関数タイプを使用するタイミングについても理解することができます。
1. 関数の宣言
関数の宣言は、
function
キーワードと、それに続く義務的な関数名で構成されます。
function
(para1, ..., paramN)
{...}
のペアで構成されるパラメータのリスト、そしてボディコードを区切る中括弧のペア。
関数宣言の例:
// function declarationfunction isEven(num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false
function isEven(num) {...}
isEven
関数を定義する関数宣言です。
この関数宣言は、現在のスコープに、関数名と同じ識別子を持つ変数を作成します。
関数変数は現在のスコープの最上位に引き上げられます。つまり、宣言の前に関数を呼び出すことができます(詳細はこの章を参照してください)。
作成された関数には名前が付けられています。つまり、関数オブジェクトのname
プロパティにその名前が保持されています。 これは、コールスタックを見るとき、つまりデバッグやエラーメッセージを読むときに便利です。
これらのプロパティを例に見てみましょう。
// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name) // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}
関数宣言 function hello(name) {...}
hello
hello
hello.name
'hello'
.
1.1 正規の関数
関数宣言は、正規の関数が必要な場合にマッチします。 規則的とは、関数を一度宣言し、後でさまざまな場所で呼び出すことを意味します。 これが基本的なシナリオです。
function sum(a, b) { return a + b;}sum(5, 6); // => 11().reduce(sum) // => 10
関数宣言は、通常の関数呼び出しと同様に、現在のスコープ内に変数を作成するため、再帰やイベント リスナーの切り離しに役立ちます。 関数式やアロー関数とは逆に、関数変数の名前でバインディングを作成しません。
例えば、階乗を再帰的に計算するには、次のように内部の関数にアクセスする必要があります。 factorial(n - 1)
となります。
const factorial = function(n) {...}
function factorial(n)
const
=
は必要ありません)。
関数宣言の重要な特性は、そのホイスティングメカニズムです。 これにより、宣言の前の関数を同じスコープ内で使用することができます。
ホイスティングはいくつかの状況で役に立ちます。 例えば、関数の実装を読まずに、スクリプトの最初にその関数がどのように呼び出されるかを確認したい場合です。 関数の実装はファイルの下の方にあったりするので、そこまでスクロールすることはないかもしれません。
関数宣言のホーミングについての詳細はこちらをご覧ください。
1.2 関数式との違い
関数宣言と関数式を混同するのは簡単です。 これらはよく似ていますが、異なる特性を持つ関数を生成します。
覚えやすいルールとしては、ステートメント内の関数宣言は常にキーワード function
で始まります。 それ以外の場合は、関数式になります(2.参照)。
以下のサンプルは、ステートメントがfunction
キーワードで始まる関数宣言です。
// Function declaration: starts with "function"function isNil(value) { return value == null;}
関数式の場合、JavaScriptのステートメントはfunction
キーワードでは始まりません(ステートメントコードのどこかに存在しています)。
// Function expression: starts with "const"const isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()const numbers = ().filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');
1.3 条件式での関数宣言
一部の JavaScript 環境では、if
for
while
のブロック内に宣言が出現する関数を呼び出すと、参照エラーが発生することがあります。
(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();
ok()
を呼び出す際に、関数宣言が条件ブロック内にあるため、JavaScriptはReferenceError: ok is not defined
を投げます。
条件式の中での関数宣言は、非厳格なモードで許可されているため、さらに混乱してしまいます。
このような状況での一般的なルールとして、条件によって関数を作成する必要がある場合は、関数式を使用します。
(function() { 'use strict'; let ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();
関数は通常のオブジェクトなので、条件に応じて変数に割り当てます。 ok()
を起動すると、エラーもなく正常に動作します。
2. 関数式
関数式は、
function
(para1, ..., paramN)
{ ... }
で囲まれたパラメータのリストがボディコードを区切っています。
関数式のいくつかのサンプル:
const count = function(array) { // Function expression return array.length;}const methods = { numbers: , sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count(); // => 3methods.sum(); // => 14
関数式は、さまざまな状況で使用できる関数オブジェクトを作成します。
- オブジェクトとして変数に割り当てられる
count = function(...) {...}
- オブジェクトにメソッドを作成する
sum: function() {...}
- 関数をコールバックとして使う
.reduce(function(...) {...})
関数式は、JavaScriptの作業用の馬です。 通常、この種の関数宣言は、矢印関数と並んで扱います(短い構文と字句の文脈を好む場合)。
2.1 名前付き関数式
関数は、名前を持たないときは無名です(name
''
):
( function(variable) {return typeof variable; }).name; // => ''
これは名前が空の文字列である無名関数です。
関数名が推測できる場合もあります。 例えば、匿名が変数に割り当てられている場合:
const myFunctionVar = function(variable) { return typeof variable; };myFunctionVar.name; // => 'myFunctionVar'
匿名関数名は'myFunctionVar'
myFunctionVar
変数名が関数名の推論に使われているからです。
式に名前が指定されている場合、これは名前付き関数式です。
- 名前付き関数が作成されます(例)。
name
プロパティに関数名が格納されています - 関数本体の中に同名の変数があり、関数オブジェクトが格納されています
上記の例で、関数式に名前を設定してみましょう。
const getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number'console.log(getType.name); // => 'funName'console.log(typeof funName); // => 'undefined'
function funName(variable) {...}
funName
name
が名前を保持しています。
funName
2.2 好意的な名前の関数式
関数式 const fun = function() {}
が変数に代入されると、エンジンによってはこの変数から関数名を推測します。 しかし、コールバックは変数に格納されずに無名の関数式として渡される場合があり、エンジンはその名前を判断できません。
次のような利点を得るために、名前付きの関数を優先し、匿名の関数を避けることは合理的です。
- 関数名を使用すると、エラーメッセージとコールスタックはより詳細な情報を表示します
- 匿名のスタック名の数を減らすことで、より快適なデバッグが可能になります
- 関数名はその関数が何をするかを表しています
- 再帰呼び出しやイベント リスナーの切り離しのために、そのスコープ内の関数にアクセスできます
3. 簡略化されたメソッドの定義
簡略化されたメソッドの定義は、オブジェクトリテラルやES2015クラスのメソッド宣言で使用できます。 関数名に続いて、パラメータのリストを括弧で囲み
(para1, ..., paramN)
{ ... }
のペアを使って定義できます。
次の例では、オブジェクトリテラルの中で省略可能なメソッド定義を使用しています。
const collection = { items: , add(...items) { this.items.push(...items); }, get(index) { return this.items; }};collection.add('C', 'Java', 'PHP');collection.get(1) // => 'Java'
add()
get()
collection
collection.add(...)
collection.get(...)
です。
名前、コロン :
add: function(...) {...}
を使用する従来のプロパティ定義に比べて、メソッド定義の短いアプローチにはいくつかの利点があります:
- 短い構文は理解しやすい
- 短縮型のメソッド定義は、関数式とは逆に、名前付きの関数を作成します。
class
構文では、短い形式でのメソッド宣言が必要です:
class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}const sun = new Star('Sun');sun.getMessage(' is shining') // => 'Sun is shining'
3.1 計算されたプロパティ名とメソッド
ECMAScript 2015では、オブジェクトリテラルやクラスにおける計算されたプロパティ名という素晴らしい機能が追加されました。
計算されたプロパティは少し異なる構文() {...}
を使用しているので、メソッド定義はこのようになります。
const addMethod = 'add', getMethod = 'get';const collection = { items: , (...items) { this.items.push(...items); }, (index) { return this.items; }};collection('C', 'Java', 'PHP');collection(1) // => 'Java'
(...) {...}
(...) {...}
は、計算済みのプロパティ名を使ったショートハンドのメソッド宣言です。
4. アロー関数
アロー関数は、パラメータのリストを含む一対の括弧
(param1, param2, ..., paramN)
=>
{...}
を使って定義されます。
矢印関数が1つのパラメータしか持たない場合は、ペアの括弧を省略することができます。
矢印関数の基本的な使い方を見てみましょう。
const absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10absValue(5); // => 5
absValue
は、数値の絶対値を計算する矢印関数です。
太い矢印を使って宣言された関数は次のような特性を持っています:
- 矢印関数は実行コンテキストを作成せず、レキシカルに取得します(関数式や関数宣言とは異なり、呼び出しに応じて独自の
this
を作成します) - 矢印関数は匿名です。
-
arguments
arguments
(...params)
を使用することは自由です。
4.1 Context transparency
this
キーワードは、JavaScriptの混乱を招く側面があります(this
についての詳しい説明はこちらの記事をご覧ください)。
関数は独自の実行コンテキストを作成するため、this
の値を検出するのが難しい場合があります。
ECMAScript 2015では、arrow関数を導入することでthis
this
.bind(this)
var self = this
を保存したりする必要がないという点で優れています。
外側の関数からthis
がどのように継承されるかを見てみましょう。
class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}const numbersObject = new Numbers();const addMethod = numbersObject.addNumber();addMethod(1);addMethod(5);console.log(numbersObject.array); // =>
Numbers
addNumber()
を提供しています。
addNumber()
this
numbersObject
addNumbers()
メソッドから語彙的に取得されるからです。
矢印機能がない場合は、手動でコンテキストを修正する必要があります。 つまり、.bind()
メソッドのような回避策を使うことになります。
//... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...
または、コンテキストを分離した変数var self = this
に格納します。
//... const self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...
コンテクストの透明化は、this
を囲んでいるコンテクストから取ってそのままにしておきたい場合に利用できます。
4.2 短いコールバック
アロー関数を作成する際、単一のパラメーターと単一のボディステートメントに対して、括弧のペアと中括弧はオプションです。
配列に 0
が含まれているかどうかを調べる関数を作ってみましょう。
const numbers = ;numbers.some(item => item === 0); // => true
item => item === 0
は簡単そうな矢印関数です。
入れ子になった短い矢印関数は読みづらいので注意が必要です。 最短の矢印関数の形で便利なのは、単一のコールバック(入れ子にしない)です。
必要に応じて、入れ子になった矢印関数を書くときは、矢印関数の拡張構文を使ってください。 そのほうが読みやすいからです。
5. ジェネレーター関数
JavaScriptのジェネレーター関数は、ジェネレーターオブジェクトを返します。
ジェネレータ関数は次のような形式で宣言できます:
a. 関数宣言フォーム function* <name>()
:
function* indexGenerator(){ var index = 0; while(true) { yield index++; }}const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
b. 関数式フォーム function* ()
:
const indexGenerator = function* () { let index = 0; while(true) { yield index++; }};const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
c. ショートハンドメソッド定義フォーム *<name>()
:
const obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}const g = obj.indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
3つのケースとも、ジェネレーター関数はジェネレーターオブジェクトg
g
は、増分された一連の数値を生成するために使用されます。
6.
6. もうひとつ: new Function
JavaScriptでは、関数はファーストクラスのオブジェクトです – 関数はfunction
型の通常のオブジェクトです。
function sum1(a, b) { return a + b;}const sum2 = function(a, b) { return a + b;}const sum3 = (a, b) => a + b;console.log(typeof sum1 === 'function'); // => trueconsole.log(typeof sum2 === 'function'); // => trueconsole.log(typeof sum3 === 'function'); // => true
関数オブジェクト型にはコンストラクタがあります。 Function
.
Function
new Function(arg1, arg2, ..., argN, bodyString)
arg1, args2, ..., argN
bodyString
が関数本体のコードとして使用されます。
2つの数字の合計を求める関数を作ってみましょう。
const numberA = 'numberA', numberB = 'numberB';const sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25
sumFunction
Function
numberA
numberB
return numberA + numberB
があります。
この方法で作成された関数は、現在のスコープにアクセスできないため、クロージャは作成できません。 クロージャは常にグローバル スコープで作成されます。
new Function
の応用例としては、ブラウザや NodeJS スクリプトでグローバル オブジェクトにアクセスするためのより良い方法が考えられます。 関数本体は実行時に評価されるため、このアプローチは、セキュリティ リスク、デバッグの困難さ、エンジンの最適化を適用する方法がないこと、エディタのオートコンプリートがないことなど、eval()
の使用上の問題点を多く受け継いでいます。
7. 結局、どちらの方法が良いのでしょうか
勝ち負けはありません。 どの宣言タイプを選択するかは、状況によります。
しかし、一般的な状況で従うことができるいくつかのルールがあります。
関数が、囲んでいる関数からthis
を使用している場合は、矢印関数が良い解決策です。 コールバック関数に1つの短いステートメントがある場合、短くて軽いコードを作成するため、矢印関数も良い選択肢となります。
オブジェクト リテラルのメソッドを宣言する際に、より短い構文を使用するには、省略可能なメソッド宣言が好ましいです。
new Function
普通に関数を宣言する方法は使うべきではありません。 主な理由は、潜在的なセキュリティ リスクが発生すること、エディタでのコードの自動補完ができないこと、エンジンの最適化が失われることなどです
。