この記事は技術レビューを必要としています。ぜひご協力ください。
この記事は編集レビューを必要としています。ぜひご協力ください。
関数は JavaScript の基本的な構成要素のひとつです。また関数は、JavaScript の手続き ― つまり、タスクや値計算を実行する文の集まりです。関数を使うには、呼び出したいスコープ内のどこかでそれを定義する必要があります。
より詳しくは JavaScript の関数に関する完全なリファレンスについての章をご覧ください。
関数を定義する
関数の宣言
関数の定義 (関数の宣言や関数定義文 (function statement) とも呼ばれます) は function
キーワードと、それに続く以下の内容で構成されます。
- 関数の名前。
- 関数への引数のリスト。丸括弧でくくり、コンマで区切ります。
- 関数を定義する JavaScript の文。波括弧
{ }
でくくります。
例えば、次のコードは square
という名前の簡単な関数を定義します :
function square(number) { return number * number; }
関数 square
は number
という名前の引数を 1 つとります。この関数は、引数(すなわち number
)の 2 乗を返すように指示する 1 つの文で構成されています。return
文は、関数が返す値を指定します。
return number * number;
プリミティブなパラメータ(数値など)は値渡しで関数に渡されます。つまり、値は関数に渡されますが、関数がパラメータの値を変更しても、この変更はグローバルな値や関数の呼び出し元の値には影響を与えません。
オブジェクト(すなわち非プリミティブ値、例えば Array
オブジェクトやユーザ定義オブジェクトなど)をパラメータとして渡すと、関数がオブジェクトのプロパティを変更した場合、その変更が関数外でも有効になります。次の例をご覧ください :
function myFunc(theObject) { theObject.make = "Toyota"; } var mycar = {make: "Honda", model: "Accord", year: 1998}; var x, y; x = mycar.make; // x は "Honda" という値になる myFunc(mycar); y = mycar.make; // y は "Toyota" という値になる //(プロパティが関数で変更されている)
関数式
ここまでの関数宣言はすべて構文的な文でしたが、関数は関数式によって作成することもできます。このような関数は無名 (anonymous) にできます。名前をつけなくてもよいのです。例えば、関数 square
は次のように定義できます :
var square = function(number) { return number * number }; var x = square(4) // x の値は 16 となる
ただし関数式は名前をつけることもでき、関数内で自身を参照することや、デバッガのスタックトレースで関数を特定することに利用できます:
var factorial = function fac(n) { return n<2 ? 1 : n*fac(n-1) }; console.log(factorial(3));
関数式は、ある関数を別の関数の引数として渡すときに便利です。次の例では map
関数を定義し、第 1 パラメータとして無名関数を指定して呼び出します:
function map(f,a) { var result = [], // 新しい配列を作成 i; for (i = 0; i != a.length; i++) result[i] = f(a[i]); return result; }
例えば、以下のように呼び出します :
map(function(x) {return x * x * x}, [0, 1, 2, 5, 10]);
これは [0, 1, 8, 125, 1000] を返します。
JavaScript では、条件に基づいて関数を定義することもできます。例えば次の関数の定義は、変数 num
が 0 に等しい場合のみ myFunc
という関数を定義します :
var myFunc; if (num == 0){ myFunc = function(theObject) { theObject.make = "Toyota" } }
これまで説明してきた関数の定義に加えて、Function
コンストラクタを、eval()
のように文字列からの関数作成に用いることができます。
メソッドは、オブジェクトのプロパティである関数のことです。オブジェクトとメソッドについて詳しくは、「オブジェクトを利用する」の章をご覧ください。
関数を呼び出す
関数を定義しても、その関数が実行されるわけではありません。関数の定義とは、ただ単に関数に名前をつけ、その関数が呼び出されたときに何をするかを指定することです。関数の呼び出しは、実際に指定したパラメータを用いて指定された動作を実行するということです。例えば、関数 square
を定義した場合、次のようにしてそれを呼び出すことができます:
square(5);
この文は 5 という引数とともに関数を呼び出します。関数は自身の文を実行し、25 という値を返します。
関数は呼び出されるスコープ内になければいけませんが、次の例のように、関数の宣言は呼び出しより後に置くことができます :
console.log(square(5)); /* ... */ function square(n) { return n*n }
関数のスコープは自身が宣言された関数内、あるいはトップレベルで宣言されたのであればプログラム全体になります。
Note: この動作は、上記の構文(すなわち function funcName(){}
)を用いて関数を定義したときに限ることに注意してください。次のコードは動作しません。
console.log(square(5)); square = function (n) { return n * n; }
関数の引数は、文字列や数値に限られてはいません。オブジェクト全体を関数に渡すこともできます。show_props
関数(「オブジェクトを利用する」の章で定義)は、オブジェクトを引数にとる関数の例です。
関数は再帰的にすることもできます。つまり、ある関数がその関数自身を呼び出すこともできるということです。例えば、ここに階乗を計算する関数を示します:
function factorial(n){ if ((n == 0) || (n == 1)) return 1; else return (n * factorial(n - 1)); }
1 から 5 までの階乗の計算は、次のようになります:
var a, b, c, d, e; a = factorial(1); // a の値は 1 となる b = factorial(2); // b の値は 2 となる c = factorial(3); // c の値は 6 となる d = factorial(4); // d の値は 24 となる e = factorial(5); // e の値は 120 となる
関数を呼び出す方法は他にもあります。関数を動的に呼び出す、関数の引数の数を変える、関数を呼び出すコンテキストを実行時に決まる特定のオブジェクトにセットするといった場合があります。関数は関数であるとともにオブジェクトでもあり、また結果としてそれらのオブジェクトはメソッドを持っています(Function
オブジェクトをご覧ください)。そうしたメソッドのひとつ、apply()
メソッドを用いることでこの目的を実現できます。
関数のスコープ
関数の内部で宣言された変数は、関数の外部からアクセスすることができません。これは、変数が関数のスコープ内でのみ定義されているためです。その一方、関数は自身が定義されたスコープ内で定義されているすべての変数や関数にアクセスできます。言い換えると、グローバルスコープで定義された関数は、グローバルスコープで定義されたすべての変数にアクセスできます。ある関数の内部で宣言された関数は、自身の親となる関数内で定義されたすべての変数や、その関数がアクセス権を持つ他の変数にもアクセスできます。
// 以下の変数はグローバルスコープで定義 var num1 = 20, num2 = 3, name = "Chamahk"; // この関数はグローバルスコープで定義 function multiply() { return num1 * num2; } multiply(); // 60 を返す // 入れ子になっている関数の例 function getScore () { var num1 = 2, num2 = 3; function add() { return name + " scored " + (num1 + num2); } return add(); } getScore(); // "Chamahk scored 5" を返す
スコープと関数スタック
再帰
関数は自身を参照し、そして呼び出すことができます。関数が自身を参照する方法は 3 種類あります :
- 関数名
arguments.callee
- 関数を参照したスコープ内変数
例えば、以下のような関数定義を考えてみましょう :
var foo = function bar() { // ここには文が来る };
関数本体の中で、以下のものはすべて同様の意味となります :
bar()
arguments.callee()
foo()
自身を呼び出す関数のことを再帰関数と言います。いくつかの点で、再帰はループに似ています。どちらも同じコードを何度も実行しますし、条件(無限ループを防ぐため、というより無限再帰を防ぐため)が必要です。例えば、以下のループは、:
var x = 0; while (x < 10) { // "x < 10" が終了条件 // 何らかの処理を行う x++; }
再帰関数とその呼び出しとに置き換えることができます :
function loop(x) { if (x >= 10) // "x >= 10" が終了条件 ("!(x < 10)" と同等) return; // 何らかの処理を行う loop(x + 1); // 再帰呼出し } loop(0);
一方で、単純な反復ループでは行えないアルゴリズムもあります。例えば、ツリー構造のすべてのノード(例えば DOM )を取得するのに、再帰を使うとより簡単に行えます :
function walkTree(node) { if (node == null) // return; // ノードに対し処理を行う for (var i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }
関数 loop
と比較して、それぞれの再帰呼出しによってさらに再帰呼出しが行われています。
どんな再帰アルゴリズムも再帰でないものに書き換えることが可能です、しかしロジックはより複雑になり、データをスタックしておく必要がたびたび出てきます。実際、再帰自体がスタックを使用しています。それが関数スタックです。
以下の例で、スタックとはどういったふるまいをするのか見ることができます :
function foo(i) { if (i < 0) return; console.log('begin:' + i); foo(i - 1); console.log('end:' + i); } foo(3); // Output: // begin:3 // begin:2 // begin:1 // begin:0 // end:0 // end:1 // end:2 // end:3
入れ子の関数とクロージャ
関数の中に関数を入れ子に(ネスト)することができます。入れ子になった(内部の)関数は、それを含んでいる(外部の)関数からはプライベートとなります。これによりクロージャが作られます。クロージャとは、環境に束縛された(式によって「閉じ込められた」)変数を自由に持たせることができる式(通常は一つの関数)のことです。
入れ子になった関数はクロージャなので、これはつまり、入れ子になった関数は内包する関数の引数と変数を「継承」することができるということです。別の言い方をすれば、内部の関数は外部の関数のスコープを持っているということです。
まとめると :
- 内部の関数へは、外部関数内の文からのみアクセスできます。
- 内部の関数はクロージャを形作ります。内部関数は外部関数の引数と変数を利用でき、その一方外部関数は内部関数の引数と変数を利用できません。
以下の実例では入れ子になった関数が示されています :
function addSquares(a,b) { function square(x) { return x * x; } return square(a) + square(b); } a = addSquares(2,3); // 13 を返す b = addSquares(3,4); // 25 を返す c = addSquares(4,5); // 41 を返す
内部の関数はクロージャとなるので、外部の関数からクロージャを呼び出し、外部と内部両方の関数に対し引数を指定することができます :
function outside(x) { function inside(y) { return x + y; } return inside; } fn_inside = outside(3); // このように考えてください : 与えられたものに 3 を加算する関数を代入します result = fn_inside(5); // 8 を返す result1 = outside(3)(5); // 8 を返す
変数の保護
inside
が返されるとき、変数 x
がどのように保護されるのかに注目してください。クロージャはそれ自身が参照しているすべてのスコープ内の引数と変数を保護することになります。それぞれの呼び出しには潜在的に異なる引数が渡されるので、それぞれの外部からの呼び出しに対し新しいクロージャが作られます。返された inside
がもはやアクセスできなくなった時にのみメモリは開放されます。
これはその他のオブジェクトの内部で参照を保持する場合と違いはないのですが、クロージャの場合は直接参照を設定せず、また情報を取得できないので、変数の保護に関してはクロージャのほうがより明白なのです。
多重入れ子関数
関数は多重に入れ子にすることができます、例えば、関数 C を含む関数 B を含む関数 A と言った具合に。ここで関数 B と関数 C はクロージャとなるので、B は A にアクセスでき、C は B にアクセスできます。さらに、C は A にアクセスできる B にアクセスできるので、C は A にもアクセスできます。このようにして、クロージャは多重スコープを導入できます。つまり関数のスコープが再帰的に包含されているのです。これをスコープチェーンと呼びます(なぜ「チェーン」と呼ぶのかは後で説明します)。
次の例を見てみましょう :
function A(x) { function B(y) { function C(z) { console.log(x + y + z); } C(3); } B(2); } A(1); // 6 がログに出力される (1 + 2 + 3)
この例では、関数 C
は関数 B
の引数 y
と関数 A
の引数 x
にアクセスしています。なぜこれが可能かというと :
- 関数
B
は関数A
に含まれたクロージャとなっています、言い換えるとB
はA
の引数と変数にアクセスできます。 - 関数
C
は関数B
に含まれたクロージャとなっています - クロージャ
B
はA
の中にあり、クロージャC
もA
の中にあるので、C
はB
、そしてさらにA
の引数と変数にアクセスできます。言い換えれば、C
はB
、A
の順でスコープがつながっている (chain) のです。
その一方で、逆は成り立ちません。A
は C
にアクセスできません、なぜなら A
は、C
を変数の一つとして持っている B
の引数や変数にはアクセスできないからです。このように, C
は B
からのみプライベートとなっています。
名前衝突
クロージャ中のスコープに同じ名前の 2 つの引数や変数がある場合、名前衝突が生じます。より内部のスコープが優先されるので、最内部にあるスコープが最優先に、一方最も外側のスコープが最も低い優先度となります。 これがスコープチェーンです。チェーンの最初は最内部のスコープ、そして最後は最外部のスコープとなります。次の例を見てみましょう :
function outside() { var x = 10; function inside(x) { return x; } return inside; } result = outside()(20); // 10 ではなく 20 を返す
文 return x
の箇所で、inside
の引数 x
と outside
の変数 x
との間に名前衝突が起きています。ここでのスコープチェーンは、 { inside
, outside
, グローバルオブジェクト } です。 したがって inside
の x
が outside
の x
より優先され、結果 10 (outside
の x
)ではなく、20 (inside
の x
)が返されます。
クロージャ
クロージャは、JavaScript でもっとも強力な機能のひとつです。JavaScript では関数の入れ子が可能であることに加えて、内側の関数が外側の関数内で定義されたすべての変数や関数に対し(外側の関数がアクセスできる、他の変数や関数すべてにも)自由にアクセスできます。しかし、外側の関数は内側の関数内で定義された変数や関数にアクセスできません。これは、内側の関数の変数に対する一種のセキュリティ機構を提供します。また、内側の関数は外側の関数のスコープにアクセスできることから、外側の関数内で定義された変数や関数は、外側の関数の生存期間を越えて残るように内側の関数によって管理された場合、外側の関数自体よりも長く残る可能性があります。クロージャは、内側の関数が何かしらの形で外側の関数のスコープ外のどこかで使用可能になった場合に作られます。
var pet = function(name) { // 外側の関数は変数 "name" を定義 var getName = function() { return name; // 内側の関数は外側の関数の変数 "name" にアクセス可能 } return getName; // 内側の関数を返すことで、外側の関数に公開する }, myPet = pet("Vivie"); myPet(); // "Vivie" を返す
上記のコードより複雑なコードにすることもできます。外側の関数の内部にある変数を操作するメソッドを含む、オブジェクトを返すことができます。
var createPet = function(name) { var sex; return { setName: function(newName) { name = newName; }, getName: function() { return name; }, getSex: function() { return sex; }, setSex: function(newSex) { if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) { sex = newSex; } } } } var pet = createPet("Vivie"); pet.getName(); // Vivie pet.setName("Oliver"); pet.setSex("male"); pet.getSex(); // male pet.getName(); // Oliver
上記の例で、外側の関数の変数 name
は内側の関数からアクセスでき、また内側の関数を通さずに内側の変数へアクセスする他の方法はありません。内側の関数の内部変数は、内側の関数の安全な保存領域として振る舞います。それらは内側の関数と連動するデータを、「永続的」かつ安全に保持します。関数は変数を割り当てる必要さえなく、また名前を持つ必要もありません。
var getCode = (function(){ var secureCode = "0]Eal(eh&2"; // 外部の関数が変更できないようにしたいコード return function () { return secureCode; }; })(); getCode(); // シークレットコードを返す
ただし、クロージャを使用する際に注意すべき落とし穴がいくつかあります。取り囲まれている関数で外側のスコープにある変数と同じ名前の変数を定義した場合、外側のスコープにある変数を再び参照する方法がなくなります。
var createPet = function(name) { // 外側の関数で変数 "name" を定義 return { setName: function(name) { // 内側の関数でも変数 "name" を定義 name = name; // 外側の関数で定義した "name" へどのようにしてアクセスするのか? } } }
魔法のような変数 this
は、クロージャ内でとても扱いにくいものです。this
の参照先は、関数が定義された場所ではなく関数が呼び出された場所に対して全面的に依存するため、注意して使用しなければなりません。
arguments オブジェクトの使用
関数の引数は、配列のようなオブジェクトで管理されます。関数内では、次のようにして渡された引数を指定することができます :
arguments[i]
ここで i
は引数の順序を表す数で、0 から始まります。関数に渡された第 1 引数は arguments[0]
となります。引数のトータルの数は arguments.length
で表されます。
arguments
オブジェクトを使用すると、宣言時の仮引数の数よりも多くの引数を用いて関数を呼び出すことができます。これによって関数に渡す引数の数が前もってわからない場合にしばしば役立ちます。arguments.length
を使用することで、実際に関数に渡された引数の数を特定することができます。そして、arguments
オブジェクトを使用して各引数にアクセスできます。
例えば、複数の文字列を連結する関数を考えます。この関数の仮引数は、連結するアイテムを区切るのに用いる文字列のみです。この関数は次のように定義されています :
function myConcat(separator) { var result = "", // リストを初期化する i; // 引数について繰り返し for (i = 1; i < arguments.length; i++) { result += arguments[i] + separator; } return result; }
この関数に引数をいくつも渡すことができます。そして、各引数を文字列の "リスト" に連結します:
// "red, orange, blue, " を返す myConcat(", ", "red", "orange", "blue"); // "elephant; giraffe; lion; cheetah; " を返す myConcat("; ", "elephant", "giraffe", "lion", "cheetah"); // "sage. basil. oregano. pepper. parsley. " を返す myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");
注記 : 変数 arguments
は "配列のような変数" であり、配列ではありません。これは数値のインデックスと length
プロパティがあることで、配列のようなものとなってはいます。しかし、配列操作のメソッドはまったく持っていません。
さらなる情報については、JavaScript リファレンスの Function
オブジェクトをご覧ください。
関数の仮引数
ECMAScript 6 から、新しい形の仮引数が 2 つあります。それがデフォルト仮引数と残余仮引数です。
デフォルト仮引数
JavaScript では、関数の仮引数はデフォルトでは undefined
となります。しかし、別のデフォルト値が設定されていれば便利だという状況もあるでしょう。デフォルト仮引数がここで役に立ちます。
以前、デフォルト値を設定する一般的な方法は、関数の本体で仮引数の値をテストし、undefined
だった場合にある値を割り当てる、というものでした。以下の例では、呼び出しの際に b
に値が割り当てられない場合、a*b
の評価の際に b
の値は undefined
となるため、multiply
を呼び出すと NaN
が返されます。しかしながら、例の 2 行目でこの問題を回避しています :
function multiply(a, b) { b = typeof b !== 'undefined' ? b : 1; return a*b; } multiply(5); // 5
デフォルト仮引数を使えば、関数本体での引数チェックはもう必要ありません。これからは、関数の最初で単純に b
に 1
を代入することができます :
function multiply(a, b = 1) { return a*b; } multiply(5); // 5
詳細については、リファレンスの「デフォルト仮引数」をご覧ください。
残余仮引数
残余仮引数の構文によって、不特定多数の引数を配列のように表すことができます。この例では、2 つ目から最後までの引数をまとめるのに残余仮引数を使っています。そして最初の引数を使って掛け算しています。この例では次の章で紹介するアロー関数を使っています。
function multiply(multiplier, ...theArgs) { return theArgs.map(x => multiplier * x); } var arr = multiply(2, 1, 2, 3); console.log(arr); // [2, 4, 6]
アロー関数
アロー関数式 (ファットアロー関数としても知られる)は関数式と比較してより短い構文を持ち、this
の値をレキシカルに束縛します。アロー関数は常に無名関数です。hacks.mozilla.org によるこのブログ記事もご覧ください : "ES6 In Depth: Arrow functions"
アロー関数の導入には 2 つの要素が絡んでいます。それは短縮形の関数とレキシカルな this
です。
短縮形の関数
関数パターンによっては、短縮形の関数がうってつけです。比較してみましょう :
var a = [ "Hydrogen", "Helium", "Lithium", "Beryllium" ]; var a2 = a.map(function(s){ return s.length }); var a3 = a.map( s => s.length );
レキシカルな this
アロー関数の導入以前は、すべての新しい関数には自身の this
値が定義されています(コンストラクタの場合は新しいオブジェクトに、Strict モードの関数呼び出しの場合は undefined に、関数が「オブジェクトのメソッド」として呼び出された場合はそのコンテキストに沿ったオブジェクトに、といったように)。 これはオブジェクト指向プログラミングにとっては厄介です。
function Person() { // この Person() コンストラクタは自身を `this` と定義します。 this.age = 0; setInterval(function growUp() { // Strict モードでない場合、この growUp() 関数は // Person() コンストラクタによる定義とは異なり、 // グローバルオブジェクトを `this` として定義します。 this.age++; }, 1000); } var p = new Person();
ECMAScript 3/5 では、this
の値をアクセス可能な別の値に割り当てることでこの問題を解決します。
function Person() { var self = this; // `self` の代わりに `that` を選ぶ人もいます。 // どちらか一方を選び、そちらだけを使うようにしましょう。 self.age = 0; setInterval(function growUp() { // このコールバックは、その値が期待通りのオブジェクトを指す // 変数 `self` を参照している。 self.age++; }, 1000); }
代わりに、bind()
を使って変数を束縛すれば growUp()
関数に適切な this
を渡すことができます。
アロー関数は関数を取り囲むコンテキストから this
の値をキャプチャーするので、下記のコードは期待通りに動作します。
function Person(){ this.age = 0; setInterval(() => { this.age++; // `this` は的確に person オブジェクトを参照する }, 1000); } var p = new Person();
定義済み関数
JavaScript には、定義済みのトップレベル関数が数種類あります :
eval()
-
eval()
メソッドは文字列として書き表された JavaScript のコードを評価します。 uneval()
-
uneval()
メソッドはオブジェクトのソースコードを表す文字列を生成します。 isFinite()
-
このグローバル関数
isFinite()
は渡された値が有限数であるかを判定します。必要であれば、引数は初めに数へと変換されます。 isNaN()
-
isNaN()
関数は値がNaN
(非数)であるかどうかを判定します。 注記 :isNaN
関数内での強制型変換は変わったルールを持っています。値が非数であるかを判定するには、代わりに ECMAScript 6 で定義されたNumber.isNaN()
か、typeof
を使うことができます。 parseFloat()
-
parseFloat()
関数は引数の文字列をパースして浮動小数点数を返します。 parseInt()
-
parseInt()
関数は引数の文字列をパースして指定された基数(数学的記数法における基数)による整数を返します。 decodeURI()
-
decodeURI()
関数は前もってencodeURI
関数によって、あるいは同様の方法で作られた{原語併記("統一資源識別子","Uniform Resource Identifier, URI"}} をデコードします。 decodeURIComponent()
-
decodeURIComponent()
メソッドは前もってencodeURIComponent
によって、あるいは同様の方法で作られた統一資源識別子 (URI) をデコードします。 encodeURI()
-
encodeURI()
メソッドは特定の文字をそれぞれ UTF-8 文字エンコーディングで表された 1 文字から 4 文字のエスケープシーケンス(サロゲートペア文字からなる文字にのみ 4 文字のエスケープシーケンスが必要)に置き換えることで統一資源識別子 (URI) をエンコードします。 encodeURIComponent()
-
encodeURIComponent()
メソッドは特定の文字をそれぞれ UTF-8 文字エンコーディングで表された 1 文字から 4 文字のエスケープシーケンス(サロゲートペア文字からなる文字にのみ 4 文字のエスケープシーケンスが必要)に置き換えることで統一資源識別子 (URI) コンポーネントをエンコードします。 escape()
-
廃止予定の
escape()
メソッドはある文字列を 16 進数によるエスケープシーケンスで置換した新しい文字列を計算します。代わりにencodeURI
かencodeURIComponent
を使用してください。 unescape()
-
廃止予定の
unescape()
メソッドはある文字列中の 16 進数によるエスケープシーケンスを、それが表す所定の文字に置換した新しい文字列を計算します。エスケープシーケンスはescape
といった関数によって提供されているかもしれません。unescape()
は廃止予定なので、代わりにdecodeURI()
かdecodeURIComponent
を使用してください。