JavaScriptにおける関数

投稿者: | 2016-05-06

ゴールデンウィークも終盤戦、予定よりゆっくり目の学習ですが焦らず行きたいと思います。

関数の定義方法

主な関数定義の方法として、関数宣言文、関数定義式、Function()コンストラクタ関数の3つが存在しますが、コンストラクタ関数の説明は省きます。その理由は、

  • eval()を使って関数本体を解析するので動作が遅い
  • クロージャが使えない

といった特徴がFunction()コンストラクタ関数による関数定義にはあり、実用性が低いためです。

関数宣言文

関数宣言文はfunction文を使って関数を定義する方法です。動作としては関数名と同じ名前の変数を宣言し、その変数に関数オブジェクトを代入します。関数宣言文は巻き上げられるので、関数定義よりも前のコードで関数を実行することができます。

console.log(addCalcStatement(4, 4));    // 関数宣言文は巻き上げができる

function addCalcStatement(x, y) {
    return x + y;
}

console.log(addCalcStatement(2, 2));     // 4

関数定義式

関数宣言文と見た目があまり変わらない関数定義式ですが、その動作は異なります。関数宣言文はコンパイル時に関数が解析されますが、関数定義式はインタプリタが定義箇所に到達したときに初めて解析されます。すなわち巻き上げられないので、関数の定義前に関数を実行することができません。

var addCalcExpremession = function (x, y) {
    return x + y;
}

console.log(addCalcExpremession(3, 3)); // 6

関数の実行

JavaScriptの関数は常に値を返します。すなわち、JavaやC#でいうところのvoid型のメソッドは存在しないということです。戻り値はJava等と同様return文で定義しますが、戻り値を指定していない場合はundefinedが返されます。

関数の引数

上記のコードを見ても明らかですが、JavaScriptの関数には引数を渡すことができます。ただ、JavaやC#とは異なる点が、デフォルトで可変長引数を持つ関数である、というところではないでしょうか。JavaScriptでは関数定義時の仮引数より実引数の数が多くても少なくても関数を実行することができます。また、実行できるだけでなく、argumentsオブジェクトを経由して、多分に設定した引数にアクセスすることができます。

function addCalcCustom(x, y) {
    if (arguments.length < addCalcCustom.length) { // arguments.lengthは実引数の数、addCalcCustomは仮引数の数
        console.log("引数が足りません");
        return;
    } else if (arguments.length > addCalcCustom.length) {
        for (var i = 2; i < arguments.length; i++) {
            console.log("この引数は余計です: ", arguments[i]);
        }
    }
    console.log("答えは:", x + y);
}

addCalcCustom(1);           // 引数が足りません
addCalcCustom(1, 2);        // 答えは:3
addCalcCustom(1, 2, 3);     // この引数は余計です:3 答えは3

関数もまたオブジェクトである

JavaScriptにおいては関数はオブジェクトです。JavaやC#に慣れ親しんだ身としては何を言っているのか意味が分かりませんでしたが、関数自体を変数に代入したり(これは関数定義式でみましたね)、関数を関数の引数にしたり、はたまた戻り値にしたり、さらにはプロパティを持たせたりすることが可能です。

// 関数を引数に受取り、受け取った関数を戻り値とする関数
var parentFunc = function (func) {
    return func;
}

var childFunc = function (x, y) {
    return x + y;
}

var execFunc = parentFunc(childFunc);   // execFuncにはchildFunc関数が代入される
console.log(execFunc(2, 2));            // childFuncと同じ関数オブジェクトを参照しているので4が出力される

parentFunc.prop = "property";
console.log(parentFunc.prop);

関数を値として扱うというと、C#のデリゲートが思い出されます。C#のデリゲートではデリゲート型を定義する際に戻り値の型、引数の型を厳密に定義する必要があったので、型付けの弱いJavaScriptだとさらに柔軟にコードが書けそうです。

関数の実行方法

何を今さらと思われるかもしれませんが、関数を実行するには宣言時に定義した名前で呼び出す、もしくは関数定義式で定義したときに関数が代入された変数を呼び出すことによって関数が実行されます。なお、今まで記事の中でお関数メソッドという二つの言葉を使っていましたが、一応使い分けはしていました(たぶん…)。オブジェクトのプロパティとして定義した関数をメソッド、としています。トップレベル(すなわちグローバル変数として)定義したものを関数と呼んでいます。ふと思ったのですが、トップレベルで定義するとグローバルオブジェクトのプロパティとなるので、関数をグローバルメソッドとか呼んだりはしないんですかね?

また、その他の関数の実行方法としてaplly()やcall()を使ったものがあります。通常、関数の呼び出し元はその関数が所属するオブジェクトを指しますが、apply()やcall()を使うことにより呼び出し元オブジェクトを変更することが可能です。

// 関数はグローバルオブジェクトに属する
var selfIntoroduction = function (age, hobby) {
    console.log("i am ", this.name, ",", age, " years old. My hobby is", hobby);
};

var name = "nanashi";
selfIntoroduction(25, "none");    // thisはグローバルオブジェクトを指すので、this.nameはグローバル変数nameを指す

var zuka = {
    "name": "shizuka",
    "age": 21,
    "hobby": "演劇鑑賞"
};
// 呼び出し元オブジェクトをzukaとして関数を呼び出すので、this.nameはzuka.nameを指す
// 後続の引数は関数に渡される
selfIntoroduction.call(zuka, zuka.age, zuka.hobby);

var richan = {
    "name": "midori",
    "age": 20,
    "hobby": "読書"
};
// 第一引数はcall()と同様に呼び出し元オブジェクトを指定する
// 第二引数は関数に渡す引数を配列表現にする必要がある
selfIntoroduction.apply(richan, [richan.age, richan.hobby]);

即時間数

JavaScriptでは定義した関数を同じ文内で即時実行するテクニックが知られています。関数定義式の右辺を()に収め、その後ろに()演算子をつけてやることによって関数を定義しつつ即実行することが可能です(最初の()はインタプリタに式として認識させるために必要です)。

(function() {
    console.log("hello, world");
})();

// 引数がある即時間数も実現可能
(function(msg) {
    console.log(msg);
})("hello, world");

さて、即時間数のメリットって何でしょう?一見、通常の関数定義&呼び出しと比べるとコード量が減っているだけにしか感じられません。と、思っていたのですが、調べてみると即時間数はスコープを汚さずに済むということが分かりました。JavaScriptはブロックスコープではなく、関数スコープで実装されているため、JavaやC#に比べるとスコープの範囲が広いです。ですので大規模なコードになると変数がぶつかってしまう可能性が比較的高いと思われます。即時間数は匿名関数を使うことができますので、グローバルオブジェクトや呼び出し元のスコープに一切の影響を与えることがない、すなわち疑似的なブロックスコープを実現することができます。

まとめ

JavaScriptの関数と、そのテクニックについてまとめました。