【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
クラスのf1
とf2
メソッドを呼び出していますが、結果が異なりますね。
これは、function
で宣言した関数のthis
は呼び出し元のthis
が、アロー関数で宣言したthis
は宣言場所でのthis
が使われるためです。
つまり、this.c1.f2();
とthis.c1f2();
の違いは、呼び出し元の違いです。

-
this.c1.f2();
では、f2
の呼び出し元はc1
(c1
にアクセスして、そこからf2
よ呼び出している)
f2
関数内でのthis
はc1
-
this.c1f2();
では、c1f2
の呼び出し元はthis
、つまりc2
f2
関数内でのthis
はc2
という違いがあります。アロー関数側は、どちらも宣言時点での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では、関数を代入するという行為を当たり前のように取るので、慣れておきましょう。
