JavaScript/関数

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

関数(かんすう、function)他の言語のサブルーチンやプロシージャと類似したサブプログラムです。JavaScriptでは関数もオブジェクト( Function )であり第一級関数です[1]

概要[編集]

関数は、0個以上のパラメータを受け取り、1つの戻り値を返すプログラムの実行単位です。

JavaScriptにかぎらず一般に多くのプログラム言語では、(JavaScriptなら 関数 console.log などの)すでにプログラム言語によって事前に準備されている組み込み済みの関数(「デフォルト関数」などとも言われる)だけでなく、さらにユーザーが自分で必要な関数を定義できます。

JavaScriptの関数は function文を使って定義・作成します。(なお、ウェブサービスに関連したプログラム言語でいうと、PHP(別のプログラム言語のひとつ)でも同様に関数の宣言のさいに function 文で定義する仕様である。なお、C言語の関数では、宣言の際に function 演算子を使わない。)

なお function はファンクションと読みます。ファンクションの和訳が「関数」です。

JavaScriptにおける関数定義の構文

function 関数名(引数1, 引数2) {
    /*   処理内容;   */
    return 戻り値;
}

JavaScriptでのコード例

function /*足し算*/ add(a, b) {
    var sum = a + b;
    return sum;
}

alert(typeof add) // function
alert(add.toString()) // コメントを含め関数本体
var three = add(1, 2); // 1 + 2
alert(three); // 3

関数は、定義すると関数名を名前とする変数に保持されます。関数は使用の際この変数を介し使われます(グローバル関数の正体は、グローバルオブジェクトのメソッドです)。

関数を呼び出すには、add(1, 2)のように関数の名前の後に()を付けて、()の中に引数として関数に渡す値を入れます。つまり

 関数名([引数1[,引数2[..., 引数n]]])

を呼び出し元のコードで記述するだけです。

なお、上記の関数の内容は数値a, bを受け取り、abを足した結果sumを返すadd関数の例です。abのように関数が受け取るデータを引数(ひきすう、parameter)といいます。

作成した関数が計算結果などを返す場合には、return文を使って返します。return文によって、return文の直後にある値を、呼び出し元の関数に返します。また、その値を戻り値(もどりち、return value)あるいは返り値(かえりち)といいます。上記コード例の場合なら sum が戻り値(返り値)です。

JavaScriptにかぎらずC言語などでも一般に、あるユーザー定義関数のreturn文が実行されると、制御が呼び出し元のコードに移るため、そのユーザー定義関数のreturn文以降のコードは実行されません。

上記コード例の場合、関数 add が呼び出されると、1はa、2はbに代入されて、関数の本体が実行され、足し算の結果が返されます。

abのように関数が受け取ると仮定された引数を仮引数(かりひきすう、かびきすう、parameter)、1や2のように関数に実際に渡した引数を実引数(じつひきすう)といいます。

数学関数[編集]

三角関数など、いくつかの数学の関数は、たとえばコサイン関数を呼び出したい場合には、

 Math.cos(引数)

のようにして使います。

なお、引数の単位はラジアンです。たとえば

 alert(Math.cos(3.141));

なら、結果は -0.9999998243 くらいになります。


定義・作成の必要は無く、最初から用意されています。

これらの Math.〇〇 といった数学関数は、形式的には、Mathオブジェクトに所属する「メソッド」といわれる処理を呼び出している(メソッドは関数プロパティとも呼ばれます)。

たとえば Math.cos なら、Mathオブジェクトのcosメソッドを呼び出すという形式になっている。

Mathオブジェクトには円周率などの定数もプロパティとして用意されています。

alert(Math.cos(Math.PI));

は、結果は正しく -1 を返します。

なお、Mathオブジェクトのメソッドは、インスタンスメソッドではなく静的メソッドなのでMathオブジェクトをコンストラクタとして new 演算子を適用してはならない。

関数リテラル[編集]

JavaScriptにおける関数はオブジェクトobject)の一種であり、ArrayオブジェクトやStringオブジェクトなど、他のオブジェクトと同じように操作できます。

構文

function(引数1[,引数2[,引数3]..[,引数n]]){
 // 処理内容
 return 戻り値;
}

コード例

var add = function(a, b){
    return a + b;
};

alert( add(1, 1) ); // 1 + 1 == 2

関数リテラルでは、関数本体に関数名をつけることはできません[2]。代わりに関数リテラルを変数に代入し変数名を使って呼び出したり右辺値化します。

関数リテラルは、関数またはラムダ式とも呼ばれます。->

関数リテラルの呼び出すときは、一般の関数と同様に呼び出し元で

関数を保持した変数名([引数1[,引数2[..., 引数n]]])

と記述すればいいだけです。

関数スコープ[編集]

関数の中でvarキーワードを用いて宣言された変数は、関数の中からしか見えません。 言い換えれば、関数の中でvarキーワードを用いて宣言された変数と、関数の外で宣言された変数は別物です。

var f = function(){
    var i = 0;
    return i + 1;
};

alert( f() ); // 1
alert(i);     // 0 は表示されず ReferenceError: i is not defined となる。

関数ブロック内での var による変数宣言は、C言語など他言語でいう「ローカル変数」に似ていますがC言語には関数スコープはなく似て非なるものです。

なお、最後の2個のalertの順番を入れ替えると、下記のようになります。

var f = function(){
    var i = 0;
    return i;
};

var i = 1;
alert(i);     // 1
alert( f() ); // 0

関数ブロック外(トップレベル)での var による変数宣言は、C言語など他言語でいう「グローバル変数」に相当し、その実体はグローバルオブジェクト(典型的には window)のプロパティです。

var x = 0;
x === window.x; // true
var と let そして const

ECMA2015(ES6)で{ }で囲まれた範囲をスコープとする letと const が導入されました。 let とconst のスコープをブロックスコープと呼びます。

関数本体もブロックなので、let が関数の外から参照されることも有りませんしvar の様な巻き上げも起こりません。letについて詳しくは『JavaScript/変数#let』の節で説明しています。 letとconstが導入されても、varの意味が変わることは有りません。 もし変わったのならば深刻な非互換性を引き起こします。 逆に、var の意味論的な位置づけを保つ必要があったので新しく let と const が導入されたといえます。 なお、関数の内外でletを使用えますし推奨されます。 たとえば、いくつか前の節で紹介した足し算を関数にしたコード例の var を let に置き換えても同様の結果です。

let に置き換え

function add(a, b) {
    let sum = a + b;
    return sum;
}
let three = add(1, 2); // 1 + 2
alert(three); // 3

const に置き換え

function add(a, b) {
    const sum = a + b;
    return sum;
}
const three = add(1, 2); // 1 + 2
alert(three); // 3

一度しか代入されない変数は、const に置き換えることも出来ます。

このことから、変数の宣言には

  1. constに出来るか?
  2. letに出来るか?(クロージャが必要か)?
  3. 上記にあてはまらない場合に限り var で宣言
  4. 宣言せずのいきなりの代入は論外!

という一般則が成り立ちます。

var なし 関数の中で変数を宣言せず代入するとグローバル変数を置き換えます(下記コードのi = 0;の箇所のことです)。

i = 3;
var f = function() {
    i = 0;
    return i + 1;
};

alert( f() ); // 1
alert(i);     // 0

グローバル変数が置き換えられているので、「3」ではなく(「3」はもはや置き換えによって値が失われた)、「0」が表示されます。

ですが、JavaScript では、このような用法は非推奨です。var のキーワード無しの変数宣言をJavaScriptは非推奨にしているからです。strictモードでは未宣言のグローバル変数への代入は ReferenceError になります。 strictモード下でグローバル変数にアクセスは、globalThis.i = 0; の様にグローバルオブジェクトのプロパティとしてアクセスします。

globalThis.i = 3;
var f = function(){
    globalThis.i = 0;
    return globalThis.i+1;
};

alert(f()); // 1
alert(globalThis.i);     // 0

関数コンストラクタを使った関数の生成[編集]

function文による定義や関数式での生成とは別に、関数コンストラクタ[3]を使って関数を生成する方法もあります。 ただし、関数コンストラクタにより生成する方法は稀にしか使われず、function文か関数式を使うのが一般的です。

関数コンストラクタの構文[編集]

new Function(引数1, 引数2, 関数の本体);

※ new はなくても構いません。

関数リテラルと似ていますが、最後の引数が関数の本体であるところが違います。 また、関数コンストラクタでは引数は全て文字列です。 加えて、関数コンストラクタはグローバルスコープで実行される関数のみを生成します。

コード例

const add = new Function('a', 'b', 'return a + b');
console.log( add(1, 1) ); // 1 + 1 == 2
console.log( add.toString());
/*
function anonymous(a,b
) {
return a + b
}
*/

プロパティ[編集]

Functionオブジェクトのプロトタイプです。

標準グローバル関数[編集]

decodeURI
decodeURIComponent
encodeURI
encodeURIComponent
eval
isFinite
isNaN
parseFloat
parseInt
void

再帰呼出し[編集]

再帰呼出しとは、関数が自分自身を呼び出すことをいいます。

// 階乗 n!
function factorial(n) {
    return n
         ? n * factorial(n - 1)
         : 1;
}
alert( factorial(5) ); // 120

// n 番目のフィボナッチ数
function fibonacci(n) {
    return n < 2
         ? n
         : fibonacci(n - 2) + fibonacci(n - 1);
}
alert( fibonacci(10) ); // 55

// a, b の最大公約数
function gcd(a, b) {
    return b
         ? gcd(b, a % b)
         : Math.abs(a);
}
alert( gcd(42, 56) ); // 14

脱出条件を間違えて無限再起にならないように注意してください。

即時関数[編集]

即時関数(そくじかんすう)とは、定義後その場で評価される関数です。。

(function(a, b){
    alert(a + b); // "3" と警告
})(1, 2);

無名再帰[編集]

無名関数の再帰を無名再帰(むめいさいき)または匿名再帰(とくめいさいき、anonymous recursion)といいます。JavaScriptで無名再帰を行うには、関数の中で自分自身を指すarguments.calleeプロパティを使用します。 strict モードでの、arguments.callee の使用は TypeError となります。

// 階乗 n!
(function(n){
    return n
         ? n * arguments.callee(n - 1)
         : 1;
})(5);

// n 番目のフィボナッチ数
(function(n){
    return n < 2
         ? n
         : arguments.callee(n - 2) + arguments.callee(n - 1);
})(10);

// a, b の最大公約数
(function(a, b){
    return b
         ? arguments.callee(b, a % b)
         : Math.abs(a);
})(42, 56);

arguments.calleeプロパティを使用せずに無名再帰を行うには、不動点コンビネータ(ふどうてんコンビネータ、fixed point combinator不動点演算子、ふどうてんえんざんし、fixed-point operator)を用います。

// Z不動点コンビネータ
var Z = function(f){
    return function(x){
        return function(y){
            return f( x(x) )(y);
        };
    }(function(x){
        return function(y){
            return f( x(x) )(y);
        };
    });
};

// 階乗 n!
Z(function(f){
    return function(n){
        return n
             ? n * f(n - 1)
             : 1;
    };
})(5);

// n 番目のフィボナッチ数
Z(function(f){
    return function(n){
        return n < 2
             ? n
             : f(n - 2) + f(n - 1);
    };
})(10);

// a, b の最大公約数
Z(function(f){
    return function(a){
        return function(b){
            return b
                 ? f(b)(a % b)
                 : Math.abs(a);
        };
    };
})(42)(56);

ラムダ計算も参照してください。

アロー関数[編集]

関数リテラルのもう1つの構文にアロー関数構文がある。 アロー関数では、他の関数リテラル異なる this はアロー関数が宣言された場所によって決まる。

以下は全て同じ意味になる。

var f = function(x) { return `x: ${x}`; }
var f = Function('x','`x: ${x}`');
var f = (x) => { return `x: ${x}`; }
var f = x => { return `x: ${x}`; }
var f = x => `x: ${x}`;

前節の不動点コンビネータをアロー関数を使って書いてみる。

// Z不動点コンビネータ
var Z = f => (
      x => y => f( x(x) )(y)
    )(x => y => f( x(x) )(y))

// 階乗 n!
Z(f => n => n ? n * f(n - 1) : 1)(5)

// n 番目のフィボナッチ数
Z(f => n => n < 2 ? n : f(n - 2) + f(n - 1))(10)

// a, b の最大公約数
Z(f => a => b => b ? f(b)(a % b) : Math.abs(a))(42)(56)

簡素に書くことが出来ることが判ると思う。

比較演算子に擬態したアロー関数に注意

次のようなコードは常に処理が実行されます。

if (a=>0) {
 // 処理
}

a=>0a>=0 の間違えですがエラーとはならず、 (a) => { return 0; } と解され、Booleanコンテキストでは真となってしまったことが原因です。

クロージャ[編集]

クロージャclosure、閉包、へいほう)とは、引数以外のすべての変数を静的スコープ(せいてきスコープ、static scoping構文スコープlexical scopingレキシカルスコープ)で解決する関数のことです。教科書などによく出てくる典型的なクロージャは、次のようなカウンタ変数を用いた例です。

// 関数を返す関数
function f(){
    var i = 0;
    return function(){
        return i++; // ここで参照される i が問題
    };
};

const g = f();
alert(typeof g); // function
alert( g() ); // 0
alert( g() ); // 1
alert( g() ); // 2
var i = 0;    // グローバルな i を書き換えても
alert( g() ); // 3 -- 値は変わらない

関数fは変数iをインクリメントして返す関数を返す関数です。f()によって生成された関数gを呼び出すと、iの値が0, 1, 2, ...と1ずつ増やして返されます。ここでiの値を書き換えても、変数gが示す関数に束縛されたiの値は変わりません。変数gが示す関数と環境はクロージャになっているからです(gを呼び出したときではなく、gが示す関数を定義したときのiを参照している)。

f() 中の変数iに注目してください。iは外側の関数式の中でvarキーワードを用いて宣言されているので、関数スコープになり、関数定義を出た時点で消滅します。しかし、関数gを呼び出すとiをインクリメントした値が返ってきます。繰り返しになりますが、クロージャとはすべての変数を、呼び出した時点ではなく定義した時点で束縛した関数と環境のことでした。f() が呼び出され内側の関数式を返した時点のiが束縛されたので、グローバルなiが見えなくなっても環境のiの値を参照しつづけられるというわけです。

ジェネレーター関数[編集]

ジェネレーター関数は、Generator オブジェクトを返す特殊な関数です。

ジェネレーター関数定義
書式: function* 関数名(引数列) { 処理 }
ジェネレーター関数式
書式: function* (引数列) { 処理 }

返されたGenerator オブジェクトは反復動作(例えば for..of)と反復構造(例えばスプレッド構文 ...iter)の両方をサポートします。

JavaScriptにありそうでないRangeオブジェクトを作ってみます。

function* Range(from = 0, to = Infinity) {
    for (let index = from; index <= to; index++) {
      yield index;
    }
}

for (const n of Range(2, 4)){ console.log(n) }
/*
 2
 3
 4
 */

console.log([...Range(1,10)]); 
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

console.log(Array.from(Range(0,12))); 
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

yield演算子は(ジェネレータではない)関数の return に相当する制御演算子です。反復動作/反復構造については、改めて詳しく説明したいと思います。

ここでは、ジェネレーター関数/Generatorオブジェクトを定義することで ArraySet の様な反復動作/反復構造をユーザーが実現できるということだけ覚えてください。


オブジェクトの関数プロパティとして再実装

let range = {
  start: 0,
  end: Infinity,

  *[Symbol.iterator]() {
    for (let value = this.start; value <= this.end; value++) {
      yield value;
    }
  }
};

range.start = 2;
range.end = 4;
for (const n of range){ console.log(n) }
/*
 2
 3
 4
 */

range.start = 1;
range.end = 10;
console.log([...range]); 
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

range.start = 0;
range.end = 12;
console.log(Array.from(range)); 
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

class の 関数プロパティとして再実装

class Range {
  constructor(start = 0, end = Infinity) {
    this.start = start;
    this.end = end;
  }

  *[Symbol.iterator]() {
    for (let value = this.start; value <= this.end; value++) {
      yield value;
    }
  }
};
for (const n of new Range(2, 4)){ console.log(n) }
/*
 2
 3
 4
 */

console.log([...new Range(1,10)]); 
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

console.log(Array.from(new Range(0,12))); 
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

varの巻上げ[編集]

varで宣言された変数を関数内で使う場合に、値の代入は代入した場所で行われるが、宣言(および宣言に伴う代入)は関数内のどこでしても関数の先頭でしたことになるという落とし穴が存在します。この挙動はvarの巻き上げ(var hoisting)と呼ばれます。

(function() {
  console.log(dream); // undefined -- なぜかReferenceErrorにならない

  var dream = true;
  document.write(dream); // true
})();

上記コードでは表示結果として、trueが表示されます。

なお、上記コードの関数は、無名関数と言われる種類の関数です。

let では、巻き上げは発生しないです。キーワード無しでも巻き上げは発生しないです。

関数内でvarで宣言した場合にだけ、巻き上げが発生します。

巻き上げが起きるのは、あくまで関数内での出来事であるので関数を用いてない場所では巻き上げは行われません。

これに巻き込まれないよう、var宣言する前には変数を使わないように注意しましょう。

varにはこういった問題があります。なので、どうしてもvarを使う場合には「常に関数の先頭で全部の変数を宣言するようにし[4]、その冒頭で必要な値の代入も済ましておく」という(まるでPASCAL言語の変数宣言のような)方法を取るのが安全です。

この手法は必要な場所のすぐ近くで変数を宣言するという他の言語のプログラマーに一般的にあるノウハウとは、JavaScriptの巻き上げ防止ノウハウのそれとは異なっている[5]ことに、私達プログラマーは留意する必要があります。

あるいは、そもそも var を使わずに const あるいは let で済ますという手もあります。const と let はブロックスコープで巻き上げは起こらない。

関数の巻上げ[編集]

関数にも巻き上げが起こる。

func("text");

function func(str) { alert(str); }

上記のコードは、func() が前方参照になっているにも関わらずエラーなく実行される("text"がalertされる)。

func("text");

const func = function (str) { alert(str); }

3行目を関数式から関数リテラルに変えconstに保持すると、 ReferenceError: Cannot access 'func' before initialization となる。

func("text");

var func = function (str) { alert(str); }

constをvarに変えると、TypeError: func is not a function。

func("text");

let func = function (str) { alert(str); }

let に変えると、ReferenceError: func is not defined となる。

このように、関数の巻き上げは関数式に限られ関数宣言に限られる。

メソッド[編集]

オブジェクトのプロパティが関数の場合を、メソッドあるいは関数プロパティと呼びます。

const obj = {
    val: 42,
    func: function() /* プロパティvalの値をヘッダーつきで文字列化します */ {
        return `val = ${this.val}`;
    },
};
console.log(obj.func()); // "val = 42"
const o2 = { val: 99 };
o2.func = obj.func;
console.log(o2.func()); // "val = 99"
console.log(o2.func.toString()); // 下の3行が表示されます。
// function() /* プロパティvalの値をヘッダーつきで文字列化します */ {
//        return `val = ${this.val}`;
//    }

メソッドは以下のように簡略表記ができます。

const obj = {
    val: 42,
    func() /* プロパティvalの値をヘッダーつきで文字列化します */ {
        return `val = ${this.val}`;
    },
};
console.log(obj.func()); // "val = 42"
const o2 = { val: 99 };
o2.func = obj.func;
console.log(o2.func()); // "val = 99"
console.log(o2.func.toString()); // 下の3行が表示されます。
// func() /* プロパティvalの値をヘッダーつきで文字列化します */ {
//         return `val = ${this.val}`;
//    }

デフォルト引数[編集]

関数の引数が省略した場合の値を定義出来ます。

従来

function Vector3( x, y, z ) {
    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0;
    this.toString = function() { return `x:${this.x}, y:${this.y}, z:${this.z}` }
}
let v = new Vector3(1, 3);
console.log("" + v); // x:1, y:3, z:0

ECMACScript 2015/ES6以降

function Vector3( x = 0, y = 0, z = 0 ) {
    Object.assign(this, {x, y, z });
    this.toString = function() { return `x:${this.x}, y:${this.y}, z:${this.z}` }
}
let v = new Vector3(1, void 0, 7);
console.log("" + v); // x:1, y:0, z:7

デフォルト引数とともにプロパティ名と同じ関数名による簡略表記をつかっている。 引数が省略された場合の他、引数の値に undefined が渡された場合も既定値が渡されたとみなす。 void 0undefined をタイプ数少なく書くイデオム。

残余引数[編集]

残余引数(Rest parameters)は、可変引数数関数を作る仕組みです[6]。 従来は arguments を使うところですが、strict モードで禁止になり非推奨なのでES6以降は残余引数を使います。

使用例

function sum(...args) {
  return args.reduce((result, current) => result + current, 0);
}
const ary = [ 2, 3, 5, 7, 11 ];
console.log(sum(...ary)); // 28
  1. 残余引数構文、引数リストを配列として保持する。
  2. よく似ているがスプレッド構文で異なる構文です。

引数の数[編集]

関数の引数の数は、Function.prototype.length プロパティ[7]で得ることが出来ます。

使用例

function add(a, b) {
    return a  + b;
}

function sum(...args) {
  return args.reduce((result, current) => result + current, 0);
}

console.log(add.length); // 2
console.log(sum.length); // 0!
console.log([].forEach.length); // 1
  1. 普通に引数数2
  2. 残余引数構文
  3. 2を返します;普通です
  4. 残余引数構文は0を返します;1ではありません
  5. 組込み標準オブジェクトのメソッドにも使えます;1を返します;Array.prototype.forEach().forEach(callback(currentValue[, index[, array]]) [, thisArg]);なので省略可能な引数の数は含まれません。

コンストラクタ[編集]

オブジェクトの生成と初期化のためのメソッドをコンストラクタと呼び、new 演算子と組み合わせて使われます。

function Complex(real, imag = 0) {
    this.real = real;
    this.imag = imag;
}
let c = new Complex(10, 14);
console.log(c); // Complex {real: 10, imag: 14}
Complex.prototype.toString = function() {
    return `r=${this.real}, i=${this.imag}`;
}
console.log(c.toString()); // r=10, i=14

同等の機能を ES6 で追加された class 宣言を使って書くと...

class Complex {
    constructor(real, imag = 0) {
      this.real = real;
      this.imag = imag;
    }
    toString() { return `r=${this.real}, i=${this.imag}` }
}
let c = new Complex(10, 14);
console.log(c); // Complex {real: 10, imag: 14}
console.log(c.toString()); // r=10, i=14

のようになります。

一見すると prototype は関係ないように見えますが

console.log(Complex.prototype.toString.toString()) // "toString() { return `r=${this.real}, i=${this.imag}` }"

と、実体は function をコンストラクタに使った記法と同じく prototype プロパティにメソッドが追加されています。

脚注[編集]

  1. ^ このことから、JavaScriptを関数型言語とされることもありますが、主要な制御構造が式ではないので一般的な認識ではありません。
  2. ^ Function::nameは設定されます。
  3. ^ コンストラクタとは違います
  4. ^ 山田祥寛『改訂新版 JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで~ 』、技術評論社、2019年8月17日 初版 第6刷、189ページ
  5. ^ 山田祥寛『改訂新版 JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで~ 』、技術評論社、2019年8月17日 初版 第6刷、189ページ
  6. ^ https://262.ecma-international.org/#sec-functiondeclarationinstantiation ECMAScript® 2020 Language Specification :: 9.2.10 FunctionDeclarationInstantiation ( func, argumentsList )
  7. ^ https://262.ecma-international.org/#sec-function-instances-length ECMAScript® 2020 Language Specification :: 19.2.4.1 length

外部リンク[編集]

このページ「JavaScript/関数」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。