【JavaScript】 アロー関数は何者!?かっこいいだけじゃない!
更新日:2024/02/27
JavaScriptで関数を定義する方法にアロー関数があります。
例えばこんな感じ。
const a = a => a;
これで関数なんですよ!
かっこいいですよね!
ということで今回は、アロー関数についてお伝えします。
アロー関数とは
アロー関数は、関数を定義する方法の一つです。
function文に置き換わるものと認識されているケースがありますが、全く同じものではありません。
かっこいいと思って既存のコードを置き換えると、動作しなくなる可能性があります。
よく理解してから、アロー関数を使用しましょう。
管理人は一時間かけてアロー関数に置き換えて、動かなくて泣いたのよ( ノД`)シクシク…
アロー関数は式である
アロー関数は正確にはアロー関数式です。
functionでの定義を例にすると、次の形式が関数宣言です。
function 関数名( 引数 ){ コード };
そして次の例が関数式。
const 変数名 = function ( 引数 ){ コード };
このほかにも関数の引数として渡したりなど、文の一部になっている関数定義が関数式です。
関数宣言と関数式については、次のページを読んでみてください。
参考:【JavaScript】 関数の定義と変数代入の違い
アロー関数は、式なので関数宣言はできません。
次のように、必ず式の一部として使用します。
const a = a => a; // 変数に代入 let a = [ 1 , 2 , 3 ].map( a => a * 2 ); // コールバック関数 let a = { a : a => a // オブジェクトのメソッド };
アロー関数の書き方
先ほどから出ている訳の分からないアロー関数で、書き方を説明します。
const a = a => a;
上のコードを関数式で書くと、次のようになります。
const a = function( a ){ return a; }
受け取った引数を、そのままリターンしているだけですね。
意味ねー!!
解説なので、一番わかりやすいものにしてあります。
少しずつ、アロー関数に置き換えていきます。
第一段階: function を ( ) と => に置き換える
まずはfunctionを消し、引数のカッコの後に => を記述します。
const a = ( a ) => { return a; }
ほとんどの関数は、この置き換えで終了です。
今回の例は、さらに変形できるのでやってみましょう。
第二段階:引数が一つなら ( ) がいらない
引数が一つだけのとき、引数を囲っているカッコを書かなくていいのです。
const a = a=> { return a; } // カッコ消していい!
引数が一つもなかったり、二つ以上のときは消せません。
const a = ( ) ;=> { コード・・・ } // カッコ必要 const a = ( a1, a2 , a3 ) => { コード・・・ } // カッコ必要
第三段階:コードが一文なら中カッコ( { } ) いらない
コードの本文が一つだけなら、中カッコがいりません。
const a = a => a; // { } カッコ消していい!
あれー?
returnが消えてるよ?
実はコードの結果が戻り値となるので、returnもいりません。
というかreturnを入れると、コンソールに次のエラーがでて止まります。
「SyntaxError: expected expression, got keyword 'return'」(FireFoxの場合)
なにげにreturnを書いてしまってハマるので、注意です。
アロー関数でオブジェクトを返す方法
次のような、オブジェクトを返す関数式があるとします。
const person = function( your_name , your_address ){ return { name : your_name , address : your_address }; }
これを今まで解説した法則でアロー関数に置き換えると、次のようになります。
const person = ( your_name , your_address ) => { name : your_name , address : your_address };
エラーになります・・・
中カッコがオブジェクト作成のカッコではなくて、関数のコードを囲むブロックとして認識されているからです。
こんな時は、カッコ( )で囲んでおきます。
この場合の( )はグループ化演算子といって、計算の優先順位を指定するためのものです。
この中に記述したものは式として取り扱われます。
const person = ( your_name , your_address ) => ({ name : your_name , address : your_address });
これでOK!
即時関数でfunctionをカッコで囲むのと同じ理由です。
function( ){ }( ); <= 関数宣言に ( ) を付けたことになりエラー
(function( ){ })(); <= 関数式に ( ) なのでOK!
this を束縛しない
アロー関数と今までの関数で一番異なるのが、アロー関数がthisを束縛しないという点です。
ネット調べるとこのように書いてあるので僕も書きましたが、よくわからないですね。
ようするに、アロー関数はthisを持っていないということです。
functionで定義した場合、内部コードでのthisはオブジェクト自身です。
function定義でのthis
console.log(this); // window const obj = { x : 100, a : function () { console.log(this); // Object { x: 100, a: a() } } }; obj.a();
しかしアロー関数はthisを持っていないので、関数の外側のthisを探しにいきます。
アロー関数でのthis
console.log(this); // window const obj = { x : 100, a : () => console.log( this , this.x ) // window undefined }; obj.a();
アロー関数の外側のthisがwindowオブジェクトなので、アロー関数内部で参照しているthisはwindowオブジェクトです。
windowオブジェクトは、xプロパティを持っていないのでundefinedとなります。
ちなみに、先ほどのオブジェクトは次のように書くことができます。
const obj = { }; obj.x = 100; obj.a = () => console.log(this,this.x);
このように書くと、"関数の外側のthis"というイメージがわかると思います。
thisを束縛するかどうかが大きな利点になるのは、コールバック関数で使用したときです。
備考:無名関数とthisの束縛
次のような、3秒毎にthisにセットされたnameプロパティの値を出力するコードがあるとします。
const a = function( name ){ this.name = name; }; a.prototype.hello = function(){ setInterval( function () { console.log( this ); // windowまたはundefined console.log( this.name + "にハロー!" ); // にハロー // ↑window.nameを見ている }, 3000 ); }; (new a( "チョコパフェ") ).hello();
上の例では、「チョコパフェにハロー」と表示されることを期待しましたが、結果は「にハロー」でした。
setIntervalが、無名関数のthisにwindowまたはundefinedを渡しているからです。
つまりthisをwindowまたはundefinedで束縛したのです。
解決策としてはbind()メソッドを使用したり、thisを他の変数に代入するのが一般的です。
bind()メソッドを使用
a.prototype.hello = function(){ setInterval( function () { console.log( this ); console.log( this.name + "にハロー!" ); }.bind( this ), 3000 ); };
他の変数に代入
a.prototype.hello = function(){ const this2 = this; setInterval( function () { console.log( this2 ); console.log( this2.name + "にハロー!" ); }, 3000 ); };
アロー関数をコールバック関数で使用した場合
アロー関数はthisを持っていないので、次のコードは期待通りに動きます。
const a = function( name ){ this.name = name; }; a.prototype.hello = function(){ console.log( this ); // Object { name: "チョコパフェ" } setInterval( () => { console.log( this) ; // Object { name: "チョコパフェ" } console.log( this.name + "にハロー!" ); // チョコパフェにハロー! },3000); }; (new a("チョコパフェ")).hello();
setIntervalで指定した無名関数は、無名関数の外側のthisを見ています。
もうbindを使ったり、コールバック関数のためだけに変数名を考えたりする必要はありませんね。
引数の羅列ができない
今までの関数は引数として受け取った変数を格納するargumentsオブジェクトを持っています。
そのため、次のような方法で引数を列挙できます。
const func = function(){ for( let i = 0 ; i < arguments.length ; i ++ ){ console.log( "[" + i + "]" + arguments[ i ] ); // 結果: [0]1ro [1]2ro [2]3ro [3]4ro } }; func("1ro" , "2ro" , "3ro" , "4ro" );
しかし、アロー関数はargumentsオブジェクトを持っていません。
上のコードをアロー関数に書き換えると、エラーになります。
const func = ()=>{
for( let i = 0 ; i < arguments.length ; i ++ ){
console.log( "[" + i + "]" + arguments[ i ] );
// 結果: ReferenceError: arguments is not defined
}
};
func("1ro" , "2ro" , "3ro" , "4ro" );
thisと同じように、外側に arguments があれば、そちらを参照します。
const func2 = function(){ const func = ()=>{ for( let i = 0 ; i < arguments.length ; i ++ ){ console.log( "[" + i + "]" + arguments[ i ] ); // 結果: [0]5ro [1]6ro [2]7ro [3]8ro } }; func("1ro" , "2ro" , "3ro" , "4ro" ); }; func2("5ro" , "6ro" , "7ro" , "8ro" );
newできない
「アロー関数は、かっこいい関数定義の方法です」って書いてあったから、何も考えずに作ったのが次のようなコード。
const a = ( name ) => { this.name=name; }; const b = new a( "1ro" );
実行したらエラーが出た。
「TypeError: a is not a constructor」
「aはコンストラクターではない」という意味です。
僕がはじめてアロー関数を使用したとき、一番悩んで、一番解決に時間がかかったエラーです。
数時間ネットで調べて得た答え。
↓ ↓ ↓
アロー関数はコンストラクタとして使用できません。newを使用するとエラーになります。
そのまんまですね
prototypeは設定できる。
a.prototype={ n:function () { console.log( "aaa" ); } }; a.prototype.n(); // 結果: aaa
だけど、newできないから意味はない。
アロー関数を使うべきでない場面
アロー関数は今までの関数定義の、新しい書き方ではありませんでした。
そのため、何でもかんでもアロー関数に置き換えようとすると、問題が続出します。
そこでアロー関数を使うべきでない場面をお伝えします。
古いブラウザでの使用
アロー関数はECMAScript 2015という仕様書で定義されました。
そのため、2015年以前のブラウザではアロー関数がサポートされていません。
そのためユーザーが使用するブラウザを制限できないときは、アロー関数の仕様を控えるべきです。
参考:ブラウザーの実装状況 | https://developer.mozilla.org/
ちなみに僕はこのサイトで、汎用的なwebツールを公開しています。
ですが特に気にせずアロー関数を使用しています。
仕様が制定されて5年以上経過しているで、ビジネス向けでなければいい気がします。
thisを使用するメソッドで使用しない
thisを参照するメソッドには、アロー関数を使うべきではありません。
thisを参照するメソッド
const a ={ x : 100, a : ( a ) => a * 2, // thisを使用していないのでOK b : ( a ) => this.x * a, // thisを使用してるのでNG c : function( a ) { return this.x * a; } // functionはthisを参照できる }; console.log( a.a( 5 ) ); // 10 console.log( a.b( 5 ) ); // NaN console.log( a.c( 5 ) ); // 500
関数外部のthisを参照するのが目的ならOKです。
thisを使用するプロトタイプメソッドで使用しない
thisを参照するプロトタイプメソッドには、アロー関数を使うべきではありません。
thisを参照するプロトタイプメソッド
const b = function() { this.x =100; }; b.prototype = { a : ( a ) => a * 2, // thisを使用していないのでOK b : ( a ) => this.x * a, // thisを使用してるのでNG c : function( a ) { return this.x * a} // thisを使用してるのでNG }; let a = new b( ); console.log( a.a( 5 ) ); // 10 console.log( a.b( 5 ) ); // NaN console.log( a.c( 5 ) ); // 500
こちらも、関数外部のthisを参照するのが目的ならOKです。
コールバック関数でthisを受け取りたいとき
一部のコールバック関数を受け取る関数では、コールバック関数呼び出し時にthisをセットします。
例えばaddEventListener()です。
html
<p id="btn">click!</p>
JavaScript
window.addEventListener( "DOMContentLoaded", function( ){ document.getElementById( "btn" ).addEventListener("click", function( ) { console.log( this ); } ); });
最初のwindow.addEventListenerは、DOMが構築されるのを待っています。
今回問題となっているのは、document.getElementById( "btn" ).addEventListenerです。
コードを実行してブラウザ上に表示された click! をクリックすると、addEventListenerで登録したコールバック関数(赤い文字)が呼び出されます。
その際addEventListenerは、コールバック関数のthisをクリックされたDOM要素に入れ替えます。
今回の例では、<p id="btn">をthisとして受け取ります。
受け取ったthisを使用して、ボタンの色やテキストを変更することができます。
thisの受け渡しについての仕組みは、次のページを参照してください。
参考:【JavaScript】 そろそろcall()とapply()を理解してみようと思う
しかし次のように、コールバック関数にアロー関数を使用すると、thisを利用してDOM要素を受け取ることができません。
JavaScript
window.addEventListener( "DOMContentLoaded", function( ){ concole.log( this ); // window document.getElementById("btn").addEventListener("click", () => console.log(this) // window ); });
アロー関数は仕様上thisを所持できないので、addEventListenerが頑張ってセットしようとしても無駄なのです。
ではアロー関数内で参照しているthisは何でしょうか?
それはwindow.addEventListener(紫色)のコールバック関数(茶色)が持っているthisです。
値はwindowオブジェクトです。
もしクリックされたボタンオブジェクトをthisで受け取りたいなら、アロー関数を使用するべきではありません。
実際にはコールバック関数の引数としてイベントオブジェクトを受け取ります。
イベントオブジェクトの中にはDOM要素も含まれているので、そちらを参照すればコールバック関数でも問題ありません。
まとめ
僕が最初に見たアロー関数の解説で、次のように書いてありました。
2 つの理由から、アロー関数が導入されました。1 つ目の理由は関数を短く書きたいということで、2 つ目の理由は this を束縛したくない、ということです。
どうみても二つ目のthisを束縛しないということの方が、一つ目よりも重要です。
しかし初心者だった僕にはthisを束縛しないの意味がわかりません。
そのため、
まあ、一つ目に書いてあることが本題だよね!
と思ってしまったのです。
そして、当時作成していたコードの関数をすべてアロー関数に書き換えたのでした。
なんで動かないんだろう・・・・
メモ帳でプログラムしていた僕は、泣きながら元に戻したのでした。
アロー関数を使用するなら、しっかりと理解してからでないとダメですね!!
でもさ、一つ目の理由はいらないよね・・・・
更新日:2024/02/27
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。