JavaScript - 関数の書き方

関数の呼び出しと参照

JavaScript では、関数はオブジェクトです。このため、ほかのオブジェクトと同じように引数として関数に渡したり、変数に代入したりすることができます。ここで重要になるのが、関数の呼び出し参照を区別することです。関数名に () を付けると関数の呼び出しになり、その関数の本体が実行され値が返されます。関数名に () を付けずに関数名だけを書くと、その関数を参照しているだけになります。関数を参照するだけでは実行はされません。

function getGreeting() {
    return "Hello world!";
}
console.log(getGreeting()); // Hello world!
console.log(getGreeting);   // function getGreeting()
関数の呼び出しと参照

最後の行が関数の参照の例です。このように関数を呼び出さずに参照することで、変数や定数への代入が可能になり別の名前で呼び出すことができるようになります。以下の例では getGreetingf という名前で呼び出しています。

const f = getGreeting; // 関数を f に代入
console.log(f());      // Hello world!
関数の参照

関数をオブジェクトのプロパティの値とすることもできます。

const o = {};       // 空のオブジェクトを定義
o.f = getGreeting;  // オブジェクト o の f プロパティの値として関数を指定
console.log(o.f()); // Hello world!
関数の参照

配列の要素として関数を代入することもできます。

const arr = [1, 2, 3];
arr[1] = getGreeting; // arr は[1, function getGreeting(), 3]になる

const message = arr[1]();
console.log(message); // Hello world!
関数の参照

JavaScript は何らかの「値」のあとに () を見つけると、その値は関数だと判断して呼び出します。上の例では arr[1] は値をもつ式で、そのあとに () が続いているので、関数だと解釈してこれを呼び出します。

関数の引数

関数を呼び出す際に情報を渡すためには引数 (パラメータ) と呼ばれるものを使います。引数は、「関数が呼び出されるまでは存在していない変数」です。

function avg(a, b) {
    return (a + b)/2;
}

console.log(avg(5, 10)); // 7.5
関数の引数

上記の関数宣言において ab仮引数と呼ばれます。関数が呼び出されると、仮引数に呼び出し側から渡された値が代入されてから本体が実行されます。

仮引数の a には 5b には 10 が値として記憶されてから、関数の本体部分が実行されます。一般的な変数と同じような役割をしますが、この関数の本体でしか値の参照や代入はできません。関数の外に同じ名前の変数があったとしても、それはまったく別の変数として扱われます。

function avg(a, b) {
    return (a + b)/2;
}

const a = 5, b = 10;
console.log(avg(a, b)); // 7.5
関数の引数

上記の例では const として宣言された ab は、関数の引数として宣言された ab とは別のものとなります。関数を呼び出すとき引数に指定した値が渡されるだけで、呼び出し側の変数自体が渡されるわけではありません。

function f(x) {
  console.log(`  関数fの中(代入の前): x=${x}`);
  x = 5;
  console.log(`  関数fの中(代入の後): x=${x}`);
}

let x = 3;
console.log(`関数fを呼び出す前: x=${x}`);
f(x);
console.log(`関数fを呼び出した後: x=${x}`);


/* 実行結果
関数fを呼び出す前: x=3
  関数fの中(代入の前): x=3
  関数fの中(代入の後): x=5
関数fを呼び出した後: x=3
*/
関数の引数

関数の中で引数xに値を代入しても、外側で使われているxの値は変わらないままです。ただし、関数に引数として渡されたオブジェクト o のプロパティを変更すると、関数の外でも o のプロパティは変更されてします

function f(o) {
    console.log(`  関数fの中、o.messageに代入する前の値:"${o.message}"`);
    o.message = "messageの値を関数fの中で設定した! ";
    console.log(`  関数fの中、o.messageに代入した後の値:"${o.message}"`);
}

let o = {
    message: "messageの初期値"
};
console.log(`関数fを呼び出す前: o.message="${o.message}"`);
f(o);
console.log(`関数fを呼び出した後: o.message="${o.message}"`);


/* 実行結果
関数fを呼び出す前: o.message="messageの初期値"
  関数fの中、o.messageに代入する前の値:"messageの初期値"
  関数fの中、o.messageに代入した後の値:"messageの値を関数fの中で設定した! "
関数fを呼び出した後: o.message="messageの値を関数fの中で設定した! "
*/
関数の引数

上記のようにプリミティブとオブジェクトでは動作が異なります。プリミティブは関数の中で変更しても呼び出し側の引数の値は変更されませんが、オブジェクトのプロパティを変更すると呼び出し側にも影響を与えます。関数 f の中で使われている変数 o は呼び出し側の o とは別の実体をもつ変数です。しかし、どちらの変数も同じオブジェクトを「参照」しています。

JavaScript のプリミティブは「値型 (value type)」であると言われます。プリミティブが渡されるときには値がコピーされるからです。これに対してオブジェクトは「参照型 (reference type)」であると言われます。オブジェクトが渡されるときには、渡すほうの変数も渡されるほうの変数も同じオブジェクトを参照するからです。

引数と関数

多くの言語では引数の個数が違えば、異なる関数として扱われます。例えば C 言語では引数のない f() と、引数をひとつ取る f(x) は別の関数として扱われます。JavaScript ではこのような区別はなく、引数の数に関係なく同じ関数 f を呼び出します。つまり、どんな関数でも任意個の引数を指定して呼び出せます。呼び出し時に引数を指定しなければ、暗黙のうちに値 undefined が指定されたものとして呼び出されます。

function f(x) {
    return `f 内の x の値: ${x}`;
}
console.log(f()); // "f 内の x の値: undefined"
引数と関数

関数定義で指定されているよりも多くの引数を渡したときにどう処理されるかは、後述する「デフォルト引数」で説明します。

引数の分割代入

引数も変数の定義と同じように分割代入を行うことができます。例えば、以下のようにオブジェクトのプロパティを変数に分配することができます。代入のときと同様、プロパティ名は識別子を表す文字列でなければなりません。また、対応するプロパティがオブジェクトに定義されていない変数には undefined が代入されます。

function getSentence({ subject, verb, object }) {
    return `${subject} ${verb} ${object}`;
}

const o = {
    subject: "I",        // 主語
    verb: "love",        // 動詞
    object: "JavaScript" // 目的語
};

console.log(getSentence(o)); // "I love JavaScript"
引数の分割代入

配列に関しても分割代入が可能です。

function getSentence([ subject, verb, object ]) {
    return `${subject} ${verb} ${object}`;
}

const arr = [ "I", "love", "JavaScript" ];

console.log(getSentence(arr)); // "I love JavaScript"
引数の分割代入

展開演算子 ... を使って引数をまとめることもできます。ただし、関数の定義で展開演算子を使う場合は、最後の引数として指定する必要があります。

function addPrefix(prefix, ...words) { 
    const prefixedWords = [];
    for(let i=0; i<words.length; i++) {
       prefixedWords[i] = prefix + words[i];
    }
    return prefixedWords;
}

console.log(addPrefix("con", "verse", "vex")); // ['converse', 'convex']
引数の分割代入

デフォルト引数

通常、引数が指定されていないと対応する仮引数に undefinedが 代入されますが、そのような場合に使われるデフォルト値を指定できます。例えば、次の例では引数 b には "default" が、引数 cには 3がデフォルト値として指定されています。

function f(a, b = "default", c = 3) {
return `${a} - ${b} - ${c}`;
}
console.log(f(5, 6, 7)); // 5 - 6 - 7
console.log(f(5, 6));    // 5 - 6 - 3
console.log(f(5));       // 5 - default - 3
console.log(f());        // undefined - default - 3
デフォルト引数

関数の戻り値

関数の呼び出し自体は「式」の一種です。式は値を持つため、関数の値は return を使って呼び出し側に戻します。次の例では "Hello world!" という文字列が関数 getGreeting() から返される値となります。

function getGreeting() {
    return "Hello world!";     // 値(戻り値)を返す
}

const message = getGreeting(); // 関数が呼び出され、戻り値が代入される
console.log(message);
関数の戻り値

関数から return で戻らない場合や、値が指定されていない return で戻る場合、関数が返す値は undefined となります。

関数式と無名関数

一般的に関数を定義するためには関数の宣言を行います。しかし、JavaScript では識別子 (関数名) を指定しない無名関数 (匿名関数) という形式があります。

識別子のない関数を呼び出すには関数式 (functionexpression) を使います。式は結果として値を返すもので、関数式は値として関数を返すものです。関数式は値を返すので、それを変数などに代入することもできますが、すぐに呼び出すこともできます。

関数式は、構文的に関数名を省略する以外は関数宣言とまったく同じです。以下の例では関数式を使って、その値を変数に代入しています。この変数を使って関数を呼び出すことができるので、実質的な効果としては関数宣言と変わりません。識別子 f を使って f() でこの関数を呼び出すことができます。

const f = function() {
    // ...
};
関数式と無名関数

無名関数は他の関数の引数として、あるいはオブジェクトのプロパティとして非常によく使われます。上記の関数式では関数名を省略しましたが、以下の例のように関数名を指定した上で関数式の値を変数に代入した場合はどうなるのでしょうか。

const g = function f() {
    // ...
}
関数式と無名関数

このように関数を作成した場合 g の方がが優先されます。関数の外で f を使ってこの関数を利用しようとすると f が定義されていないというエラーになります。

const g = function f(stop) { 
    if(stop) {
        console.log('f停止');
        return;
    }
    else {
        console.log('fは停止していない');
        f(true);
    }
};

g(false);
console.log("----");
g(true);

/* 実行結果
fは停止していない
f停止
----
f停止
*/
関数式と無名関数

関数の中では f でこの関数を参照し、外からは g でこの関数を参照しています。

関数宣言と関数式はよく似ていますが、コンテキスト (周囲の状況) によって区別されます。関数宣言が式として使われれば関数式となり、そうでない場合は関数宣言となります。

両者の区別は実質的にはほとんどありません。あとで呼び出す予定で名前付きの関数を定義するのならば関数定義、関数を作って何かに代入したり他の関数に渡したりするのならば関数式を使います。

関連記事

Category:
プログラミング
公開日:
更新日:
Pageviews:
19
Shares:
1
Tag:
JavaScript
コードレシピ