
const 変数で関数を定義するより、昔ながらの関数ステートメントを使うべきとき|ES2015
僕たちが昔、JavaScriptを書き始めた頃、Hello Worldの関数はこういう風に書いてたはずです。
function helloWorld() {
return ‘Hello World!’;
}
しかし、最近のクールな若者は、関数をこんな風に書くみたいです。
const helloWorld = () => 'Hello World';
この文法は、ES2015 で採用された美しい一行コードでして、アローファンクションは、ES2015で採用された最も人気のある仕様です。
私はアローファンクションが大好きですが、それだとしても、いまだに私は、トップレベルの関数を定義するときは、昔ながらの関数定義をします。
何故ならば、アンクル・ボブ・マーティンのこの言葉は、その理由を説明しているからです。
“読む時間と書く時間の比率は、10対1をはるかに超えています。私たちは、新しいコードを書くために、常に古いコードを読んでいます。
この比率が非常に高いので、たとえ書くのが難しくなっても、コードを読むのは簡単であってほしいのです。”
関数ステートメントは、関数式に比べて2つの明確な利点があります。
利点1 : 意図の明確性
1日に何千行ものコードに目を通すとき、プログラマーの意図をできるだけ早く、簡単に把握できると便利です。
こちらを見てください。
const maxNumberOfItemInCart = ...;
文字を全部読んでも、省略記号が関数を表しているのか、何か他の値を表しているのか、まだわかりませんよね。そうかもしれない。
実際はこういう風に関数は書けるわけですが、変数にも見えるわけです。
const maxNumberOfItemsInCart = (statusPoints) => statusPoints * 10;
ファンクションステートメントを使えば、そのような曖昧さはありません。
function maxNumberOfItemsInCart(statusPoints) {
return statusPoints * 10;
}
利点2:宣言の順番=実行の順番
理想的には、
コードが実行されると予想される順序で、
多かれ少なかれ宣言したいですよね。
constキーワードで宣言された値は、実行が到達するまでアクセスできません。
なので、以下のコードはエラーになります。
sayHelloTo('Bill')
const sayHelloTo = (name) => 'Hello $(name)';
言い換えれば、JavaScriptは “sayHelloTo “の宣言をバインドします。
つまり、最初に読み込んで、その値を保持するための空間をメモリ上に作ります。
しかし、実行中に到達するまで、”sayHelloTo “を何も設定しません。
sayHelloTo “がバインドされてから、”sayHelloTo “が初期化されるまでの時間を、
TDZ(Temporal Dead Zone)と呼びます。
ES2015をブラウザで直接使用している場合は以下のコードも実際にはエラーになります。
if(thing) {
console.log(thing);
}
const thing = 'awesome thing';
上のコードは、”const “ではなく “var “を使って書くと、エラーは発生しません。
なぜなら、varはバインドされたときにundefinedとして初期化されるのに対し、
constはバインドされたときにまったく初期化されないからです。
話は変わりますが…。
関数文には、このTDZの問題はありません。以下は完全に有効です。
sayHelloTo(‘Bill’);
function sayHelloTo(name) {
return `Hello ${name}`;
}
これは、関数文がバインドされた時点で、
つまりコードが実行される前に初期化されるからです。
つまり、いつ関数を宣言しても、コードの実行が始まると同時に、
その辞書的な範囲で利用できるようになるのです。
先ほど説明したように、私たちは逆さまに見えるコードを書かなければなりません。
最下層の機能から始めて、上に向かってやっていかなければならないのです。
実際に、コードの先頭にAPIのちょっとした概要を提供できたらいいと思いませんか?
関数文を使えば、それが可能になります。
この(多少作為的な)ショッピングカートモジュールを見てみましょう。
export {
createCart,
addItemToCart,
removeItemFromCart,
cartSubTotal,
cartTotal,
saveCart,
clearCart,
}
function createCart(customerId) {...}
function isValidCustomer(customerId) {...}
function addItemToCart(item, cart) {...}
function isValidCart(cart) {...}
function isValidItem(item) {...}
関数式の場合は以下のようになります。
const _isValidCustomer = (customerId) => ...
const _isValidCart = (cart) => ...
const _isValidItem = (item) => ...
const createCart = (customerId) => ...
const addItemToCart = (item, cart) => ...
export {
createCart,
addItemToCart,
removeItemFromCart,
cartSubTotal,
cartTotal,
saveCart,
clearCart,
}
これを、小さな内部機能をたくさん持った大きなモジュールとして想像してみてください。
あなたはどちらを選びますか?
宣言する前に何かを使うことは不自然であり、意図しない結果を招く可能性があると主張する人がいます。
どちらかの方法が良いというのは、それぞれの意見でしょうが、
しかし、私に言わせれば コードはコミュニケーションです。
良いコードはストーリーを語ります。
機械のためにコードを最適化することは、コンパイラやトランスパイラ、ミニファイアーやウグイスファイアーに任せておいて、
私は人間の理解のためにコードを最適化したいのです。
アロー関数を使うとき
とはいえ、繰り返しになりますが、私がアロー関数は大好きなんです。
私は通常、小さな関数を高次の関数に値として渡すために、アロー関数を使います。
例えば、プロミス、マップ、フィルター、リデュースでも矢印関数を使います。
最後に、それらを表すコードを少し見て、終わりにしますね。
const goodSingers = singers.filter((singer) => singer.name !== 'Justin Bieber');
function tonyMontana() {
return getTheMoney()
.then((money) => money.getThePower())
.then((power) => power.getTheWomen());
}
*この記事は英語の記事でめっちゃ面白いと思ったので、それの翻訳ベースです、より詳しく読みたい方は本家の記事へどうぞ。