【Javascript】 functionとアロー関数

Javascriptにおける関数は、functionsを用いたものとアロー関数の2種類あります。

この2種類は単に書き方が異なるだけでなく、thisの扱いが異なるなどの差異があるため、合わせて紹介します。

function

普通の関数の宣言は、functionをつけて宣言します。仮引数は変数名だけでOKで、letなどは不要です。

function f(arg1, arg2) {
    console.log(arg1);
    console.log(arg2);
    retrun 10; // returnは一般的な書き方
}
f("foo", 100);
foo
100

Javascriptの関数は、グローバルだけでなく、殆どの場所で宣言できます。宣言後はいつでも利用できます。

const x = 1;
if (x) {
    function f() {
        console.log("aa");
    }
    f();
}
f(); // エラーではない
aa
aa

巻き上げ

functionでは、巻き上げが行われます。つまり、宣言より前に呼び出しがあっても利用可能です。

f();

function f() {
    console.log("OK");
}

関数外の変数を利用する場合、関数呼び出し後に宣言される変数を利用しているとエラーの元になるので注意しましょう。

const s1 = "s1はOK";
f();
const s2 = "NG";

function f() {
    console.log(s1);
    console.log(s2); // ReferenceError: Cannot access 's2' before initialization
}

無名関数

functionに名前をつけなければ無名関数になります。実引数に入れる、変数に代入するのに利用します。巻き上げはありません。

const hoge = function (v) {
    return v + 100;
}

console.log(hoge(500));
600

アロー関数

(args) => {/* ... */}という書き方です。

const f = (arg1, arg2) => {
    console.log(arg1, arg2);
    return 100;
}
console.log(f("abc", "xyz"));
abc xyz
100

アロー関数には省略記法があり、どちらも重要です。

  • 本体が1行の場合, {}を省略したうえで、その行がreturnされる
    この記法だとforやifなどは使えないため、本当に1行の場合になる
  • 仮引数が1個の場合, ()を省略可能
    0個の場合は() => ...のように明示する必要がある

例えば、このような書き方ができます。

const f = arg => arg * 2;
console.log(f(100));
200

arg => arg * 2を略さずに書くと、(arg) => { return arg * 2; }となります。

巻き上げ

functionと違い、巻き上げはありません。変数に代入して利用するため、その行が計算されるときに初めて関数が作られるためです。

また、functionのように、関数に直接名前をつけることはできません。

f(); // ReferenceError: Cannot access 'f' before initialization
const f = () => {};

スコープ

主に変数に代入するため、その変数のスコープが適用されます。functionの場合でも、無名関数を変数に代入するなら同様の挙動です。

{
    const f = () => {};
    f(); // OK
}
f(); // ReferenceError: f is not defined

thisの解釈の違い

多くの人がハマる落とし穴です。functionとアロー関数では、thisの解釈が異なります

// メソッドが呼ばれる側
class C1 {
    constructor() {
        this.s = "C1 object";

        // アロー関数
        this.f1 = () => {
            console.log(this.s);
        }
        // 普通の関数
        this.f2 = function () {
            console.log(this.s);
        }
    }
}

// メソッドを呼ぶ側
class C2 {
    constructor() {
        this.s = "C2 object";

        this.c1 = new C1();

        // C1クラスのインスタンスの関数を代入
        this.c1f1 = this.c1.f1;
        this.c1f2 = this.c1.f2;
    }

    f() {
        this.c1.f1();
        this.c1.f2();
        this.c1f1();
        this.c1f2();
    }
}

const c2 = new C2();
c2.f();
C1 object
C1 object
C1 object
C2 object

いずれもC2クラスのfメソッドから、C1クラスのf1f2メソッドを呼び出していますが、結果が異なりますね。

これは、functionで宣言した関数のthisは呼び出し元のthisが、アロー関数で宣言したthisは宣言場所でのthisが使われるためです。

つまり、this.c1.f2();this.c1f2();の違いは、呼び出し元の違いです。

thisの違い
  • this.c1.f2();では、f2の呼び出し元はc1 (c1にアクセスして、そこからf2よ呼び出している)
    f2関数内でのthisc1
  • this.c1f2();では、c1f2の呼び出し元はthis、つまりc2
    f2関数内でのthisc2

という違いがあります。アロー関数側は、どちらも宣言時点でのthisが使われています。よくthisが束縛されると言いますが、このことです。

今回の例はたまたまフィールド名が同じでしたが、そうでなかった場合はundefinedが取得されてエラーになったりします。

また、クラスで普通に宣言したメソッドは、functionと同じ挙動です。

// メソッドが呼ばれる側
class C1 {
    constructor() {
        this.s = "C1 object";

        // アロー関数
        this.f1 = () => {
            console.log(this.s);
        }
    }

    f2() {
        console.log(this.s);
    }
}

// メソッドを呼ぶ側
class C2 {
    constructor() {/* ... */}

    f() {
        this.c1.f1();
        this.c1.f2();
        this.c1f1();
        this.c1f2();
    }
}

const c2 = new C2();
c2.f();
C1 object
C1 object
C1 object
C2 object

Javascriptでクラスを扱い始めると頻繁に引っかかる罠なので、差をしっかりと把握しましょう!

即時関数

宣言した直後に実行される関数です。これは、無名関数を工夫した書き方です。

(() => {
    console.log("実行");
})();
実行

ここで、行を分けてみると、こうなります。

const f = () => {
    console.log("実行");
}

f();

これを1行にまとめて、変数への代入をなくしたものが、上で紹介した即時関数と呼ばれるものです。つまり。() => {/*...*/}のアロー関数に()を付けて関数呼び出ししたのが即時関数です。

ここで、() => {...}()だと文法エラーなので、(() => {...})()のように、関数本体を括弧で囲みます。

functionでも同じことができます。

(function () {
    console.log("実行");
})()

まとめ

Javascriptでの関数の使い方と、一番の特徴であるthisの挙動の違いについて説明しました。

特にJavascriptでは、関数を代入するという行為を当たり前のように取るので、慣れておきましょう。

thumb

役に立ったらシェアしよう!