関数

This article is in need of a technical review.

This article is in need of an editorial review.

This translation is in progress.

関数は JavaScript の基本的な構成要素のひとつです。また関数は、JavaScript の手続き ― つまり、タスクや値計算を実行する文の集まりです。関数を使うには、呼び出したいスコープ内のどこかでそれを定義する必要があります。

より詳しくは JavaScript の関数に関する完全なリファレンスについての章をご覧ください。

関数を定義する

関数の宣言

関数の定義 (関数の宣言や関数定義文 (function statement) とも呼ばれます) は function キーワードと、それに続く以下の内容で構成されます。

  • 関数の名前。
  • 関数への引数のリスト。丸括弧でくくり、コンマで区切ります。
  • 関数を定義する JavaScript の文。波括弧 { } でくくります。

例えば、次のコードは square という名前の簡単な関数を定義します:

function square(number) {
  return number * number;
}

関数 squarenumber という名前の引数を 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" という値になる
                //(プロパティが関数で変更されている)

注記 : 引数に新たなオブジェクトを代入しても、関数の外部には影響が及びません。これは、オブジェクトのいずれかのプロパティの値ではなく、引数の値を変更しているためです。

function myFunc(theObject) {
  theObject = {make: "Ford", model: "Focus", year: 2006};
}

var mycar = {make: "Honda", model: "Accord", year: 1998};
var x, y;

x = mycar.make; // x は "Honda" という値になる

myFunc(mycar);
y = mycar.make; // y はここでも "Honda" という値になる 

関数式

ここまでの関数宣言はすべて構文的な文でしたが、関数は関数式によって作成することもできます。このような関数は無名 (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 種類あります :

  1. 関数名
  2. arguments.callee
  3. 関数を参照したスコープ内変数

例えば、以下のような関数定義を考えてみましょう :

var foo = function bar() {
   // ここには文が来る
};

関数本体の中で、以下のものはすべて同様の意味となります :

  1. bar()
  2. arguments.callee()
  3. 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 にアクセスしています。なぜこれが可能かというと :

  1. 関数 B は関数 A に含まれたクロージャとなっています、言い換えると BA の引数と変数にアクセスできます。
  2. 関数 C は関数 B に含まれたクロージャとなっています
  3. クロージャ BA の中にあり、クロージャ CA の中にあるので、CBそしてさらに A の引数と変数にアクセスできます。言い換えれば、 CB、 A の順でスコープがつながっている (chain) のです。

その一方で、逆は成り立ちません。AC にアクセスできません、なぜなら A は、C を変数の一つとして持っている B の引数や変数にはアクセスできないからです。このように, CB からのみプライベートとなっています。

名前衝突

クロージャ中のスコープに同じ名前の 2 つの引数や変数がある場合、名前衝突が生じます。より内部のスコープが優先されるので、最内部にあるスコープが最優先に、一方最も外側のスコープが最も低い優先度となります。 これがスコープチェーンです。チェーンの最初は最内部のスコープ、そして最後は最外部のスコープとなります。次の例を見てみましょう :

function outside() {
  var x = 10;
  function inside(x) {
    return x;
  }
  return inside;
}
result = outside()(20); // 10 ではなく 20 を返す

return x の箇所で、inside の引数 xoutside の変数 x との間に名前衝突が起きています。ここでのスコープチェーンは、 { inside, outside, グローバルオブジェクト } です。 したがって insidexoutsidex より優先され、結果 10 (outsidex)ではなく、20 (insidex)が返されます。

クロージャ

クロージャは、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 オブジェクトをご覧ください。

定義済み関数

JavaScript には、定義済みのトップレベル関数がいくつかあります:

以下の章で、これらの関数を説明します。なお全関数の詳細情報については、JavaScript リファレンスをご覧ください。

eval 関数

eval 関数は JavaScript のコード文字列を、特定のオブジェクトを参照することなく評価します。eval の構文は次のとおりです:

eval(expr);

ここで expr は、評価される文字列です。

文字列が式を表している場合は、eval はその式を評価します。また 1 つ以上の JavaScript の文を表している場合は、eval はその式を実行します。eval のコードのスコープは、呼び出し元コードのスコープと同じです。演算式を評価するために eval を呼び出さないでください。JavaScript は自動的に演算式を評価します。

isFinite 関数

isFinite 関数は引数を評価し、それが有限数であるかを判断します。isFinite の構文は次のとおりです:

isFinite(number);

ここで number は、評価する数値です。

引数が NaN、正の無限大、または負の無限大である場合、このメソッドは false を返し、そうでない場合は true を返します。

次のコードはクライアントの入力をチェックし、それが有限数であるかを判断します。

if(isFinite(ClientInput)){
   /* 適当な処理 */
}

isNaN 関数

isNaN 関数は引数を評価し、それが "NaN" (not a number; 非数) であるかを判断します。isNaN の構文は次のとおりです:

isNaN(testValue);

ここで testValue は、評価したい値です。

parseFloat および parseInt 関数は、非数を渡したときに "NaN" を返します。isNaN は "NaN" が渡された場合は trne を、そうでない場合は false をそれぞれ返します。

次のコードは floatValue を評価し、それが数値であるかを判断し、その結果に応じてプロシージャを呼び出します:

var floatValue = parseFloat(toFloat);

if (isNaN(floatValue)) {
   notFloat();
} else {
   isFloat();
}

parseInt 関数および parseFloat 関数

2 つの「パース」関数、parseInt および parseFloat は、引数に文字列が与えられたときに数値を返します。

parseFloat の構文は次のとおりです:

parseFloat(str);

parseFloat は引数である文字列 str をパースし、浮動小数点数を返そうとします。符号 (+ または -)、数字 (0-9)、小数点、または指数以外の文字に出くわすと、そこまでの値を返し、その文字および後続の文字すべてを無視します。最初の文字が数値に変換できない場合は、"NaN" (not a number; 非数) を返します。

parseInt の構文は次のとおりです:

parseInt(str [, radix]);

parseInt は第 1 引数である文字列 str をパースし、指定した radix (基数) の整数を返そうとします。radix は省略可能な第 2 引数です。例えば、10 という基数では 10 進数に変換し、8 では 8 進数、16 では 16 進数というように変換します。10 より大きな基数では、アルファベットで 9 より大きい数を表します。例えば 16 進数 (基数 16) では、A から F が使用されます。

parseInt は指定した基数の数値ではない文字に出くわすと、その文字と後続の文字すべてを無視し、それまでにパースした整数の値を返します。最初の文字を指定した基数の値に変換できない場合は、"NaN" を返します。parseInt は文字列を切り捨てて整数の値にします。

Number 関数および String 関数

Number および String 関数は、オブジェクトを数値や文字列に変換します。これらの関数の構文は以下のとおりです:

var objRef;
objRef = Number(objRef);
objRef = String(objRef);

ここで objRef は、オブジェクト参照を表します。Number 関数は、オブジェクトの valueOf() メソッドを用います。String 関数は、オブジェクトの toString() メソッドを用います。

次の例では、Date オブジェクトを理解できる文字列に変換します。

var D = new Date(430054663215),
    x;
x = String(D); // x は "Thu Aug 18 04:37:43 GMT-0700 (Pacific Daylight Time) 1983" と等しい

次の例では、String オブジェクトを Number オブジェクトに変換します。

var str = "12",
    num;
num = Number(str);

この結果を確認できます。DOM の write() メソッドと JavaScript の typeof 演算子を使用してください。

var str = "12",
    num;
document.write(typeof str);
document.write("<br/>");
num = Number(str);
document.write(typeof num);

escape 関数および unescape 関数

escape および unescape 関数は非 ASCII 文字に対して適切に動作せず、非推奨になりました。JavaScript 1.5 以降では encodeURIdecodeURIencodeURIComponent、および decodeURIComponent を使用してください。

escape および unescape 関数は、文字列のエンコードやデコードを行います。escape 関数は ISO Latin 文字セットで表された、引数の 16 進エンコーディングを返します。unescape 関数は、指定した 16 進エンコーディングの値に対する ASCII 文字列を返します。

これらの関数の構文は以下のとおりです:

escape(string);
unescape(string);

これらの関数は、主にサーバサイド JavaScript で URL 中の名前と値のペアのエンコードやデコードに使用されます。

ドキュメントのタグと貢献者

Contributors to this page: x2357, lv7777, ethertank, yyss, happysadman, Potappo, Electrolysis
最終更新者: x2357,
サイドバーを隠す