と により() => {}
、function () {}
ES6 で関数を記述する 2 つの非常によく似た方法が実現します。他の言語では、ラムダ関数は匿名であることによって区別されることが多いですが、ECMAScript ではどの関数も匿名にすることができます。2 つのタイプにはそれぞれ固有の使用ドメイン (つまり、this
明示的にバインドする必要があるか、明示的にバインドしない必要があるか) があります。これらのドメイン間では、どちらの表記法でも機能するケースが多数あります。
ES6 の矢印関数には少なくとも 2 つの制限があります。
new
作成時に使用することはできません。prototype
this
初期化時にスコープへのバインドを修正
これら 2 つの制限を除けば、矢印関数は理論的にはほとんどどこでも通常の関数を置き換えることができます。実際にそれらを使用する正しいアプローチは何ですか? 矢印関数は、たとえば次のように使用する必要があります。
- 「動作する場所ならどこでも」、つまり、関数は変数について不可知である必要はなく
this
、オブジェクトを作成しない場所ならどこでも。 - 特定のスコープにバインドする必要があるイベントリスナーやタイムアウトなど、「必要な場所すべて」のみ
- 「短い」関数では可能だが、「長い」関数では不可能
- 他の矢印関数を含まない関数のみ
私は、ECMAScript の将来のバージョンで適切な関数表記を選択するためのガイドラインを探しています。ガイドラインは、チーム内の開発者に教えることができるように明確である必要があり、また、関数表記法を何度も切り替えてリファクタリングする必要がないように一貫性がなければなりません。
この質問は、今後の ECMAScript 6 (Harmony) のコンテキストでコード スタイルについて考えたことがあり、すでにこの言語を使っている人に向けたものです。
ベストアンサー1
少し前に、私たちのチームはすべてのコード(中規模のAngularJSアプリ)を
Traceur を使用してコンパイルされたJavaScriptに移行しました。
バベル私は現在、ES6 以降の関数に対して次の経験則を使用しています。
function
グローバル スコープおよびプロパティで使用しますObject.prototype
。class
オブジェクトコンストラクターに使用します。- 他の場所でも使用でき
=>
ます。
ほとんどすべての場所で矢印関数が使用されるのはなぜですか?
- スコープの安全性: 矢印関数が一貫して使用される場合、すべてがルートと同じものを使用することが保証されます
thisObject
。 1 つの標準関数コールバックが多数の矢印関数に混在している場合、スコープが混乱する可能性があります。 - コンパクトさ: 矢印関数は読みやすく、書きやすいです。(これは偏った意見のように思われるかもしれないので、後でいくつか例を挙げます。)
- 明確さ: ほとんどすべてが矢印関数である場合、
function
スコープを定義するために、正規表現がすぐに目立ちます。開発者は、常に次の上位のfunction
ステートメントを調べて、それが何であるかを確認できますthisObject
。
グローバル スコープまたはモジュール スコープで常に通常の関数を使用するのはなぜですか?
- アクセスしてはならない関数を示します
thisObject
。 - オブジェクト
window
(グローバル スコープ) は明示的にアドレス指定するのが最適です。 - 多くの
Object.prototype
定義はグローバル スコープ内に存在します (String.prototype.truncate
など)。これらは一般的に 型である必要がありますfunction
。function
グローバル スコープで一貫して を使用すると、エラーを回避できます。 - グローバル スコープ内の多くの関数は、古いスタイルのクラス定義のオブジェクト コンストラクターです。
- 関数には名前を付けることができます1
function foo(){}
。これには 2 つの利点があります: (1) 特に他の関数呼び出しの外側で書くよりも書きやすいconst foo = () => {}
。 (2) 関数名はスタック トレースに表示されます。すべての内部コールバックに名前を付けるのは面倒ですが、すべてのパブリック関数に名前を付けるのはおそらく良い考えです。 - 関数宣言は吊り上げられた、(宣言される前にアクセスできることを意味します)、これは静的ユーティリティ関数で便利な属性です。
オブジェクトコンストラクタ
矢印関数をインスタンス化しようとすると例外がスローされます。
var x = () => {};
new x(); // TypeError: x is not a constructor
したがって、矢印関数に対する関数の主な利点の 1 つは、関数がオブジェクト コンストラクターとしても機能することです。
function Person(name) {
this.name = name;
}
しかし、機能的に同一の2つのECMAScript Harmonyドラフトクラスの定義ほぼ同じくらいコンパクトです:
class Person {
constructor(name) {
this.name = name;
}
}
最終的には、以前の表記法の使用は推奨されなくなると思います。オブジェクト コンストラクター表記法は、オブジェクトがプログラムによって生成される単純な匿名オブジェクト ファクトリーでは引き続き使用される可能性がありますが、それ以外の用途ではあまり使用されません。
オブジェクト コンストラクターが必要な場合は、上記のように関数を に変換することを検討する必要がありますclass
。この構文は匿名関数/クラスでも機能します。
矢印関数の可読性
通常の関数に固執する最も良い理由は、スコープの安全性など気にせず、矢印関数は通常の関数よりも読みにくいということでしょう。コードがそもそも機能的でない場合は、矢印関数は必要ないと思われるかもしれませんし、矢印関数が一貫して使用されていないと見栄えが悪くなります。
ECMAScriptはECMAScript 5.1で関数型が導入されて以来、かなり変化しました。Array.forEach
これらArray.map
の関数型プログラミング機能により、以前はforループを使用していた関数を使用することができます。非同期JavaScriptはかなり普及しています。ES6では、Promise
オブジェクトは、さらに多くの匿名関数を意味します。関数型プログラミングには後戻りできません。関数型 JavaScript では、矢印関数は通常の関数よりも好まれます。
たとえば、この(特に紛らわしい)コード3を見てみましょう。
function CommentController(articles) {
this.comments = [];
articles.getList()
.then(articles => Promise.all(articles.map(article => article.comments.getList())))
.then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
.then(comments => {
this.comments = comments;
})
}
通常の関数を使用した同じコード:
function CommentController(articles) {
this.comments = [];
articles.getList()
.then(function (articles) {
return Promise.all(articles.map(function (article) {
return article.comments.getList();
}));
})
.then(function (commentLists) {
return commentLists.reduce(function (a, b) {
return a.concat(b);
});
})
.then(function (comments) {
this.comments = comments;
}.bind(this));
}
矢印関数はいずれも標準関数に置き換えることができますが、置き換えてもメリットはほとんどありません。どちらのバージョンの方が読みやすいでしょうか? 私としては最初のバージョンだと思います。
矢印関数を使用するか通常の関数を使用するかという問題は、時間の経過とともにあまり重要ではなくなると思います。ほとんどの関数は、キーワードが不要になるクラス メソッドになるfunction
か、クラスになります。関数は、を通じてクラスにパッチを適用する場合に引き続き使用されます。Object.prototype
当面は、function
クラス メソッドまたはクラスであるべきものに対してキーワードを予約することをお勧めします。
ノート
- 名前付き矢印関数はES6仕様では延期されている将来のバージョンで追加される可能性もあります。
- ドラフト仕様によれば、クラスがキーワードを使用しない限り、「クラス宣言/式は、関数宣言とまったく同じようにコンストラクタ関数/プロトタイプ ペアを作成します」
extend
。小さな違いは、クラス宣言は定数であるのに対し、関数宣言は定数ではないことです。 - 単一ステートメントの矢印関数内のブロックに関する注意: 副作用のみ (割り当てなど) のために矢印関数が呼び出される場合は、常にブロックを使用することをお勧めします。 こうすることで、戻り値を破棄できることが明確になります。