ECMAScript 6でアロー関数を使用する必要があるのはいつですか?


406

質問は、来たるECMAScript 6(ハーモニー)のコンテキストでコードスタイルについて考え、すでに言語を使用したことがある人を対象としています。

() => {}し、function () {}我々はES6で関数を作成するには、2つの非常に類似した方法を取得しています。他の言語では、ラムダ関数は匿名であることで区別されることがよくありますが、ECMAScriptでは任意の関数を匿名にすることができます。2つのタイプにはそれぞれ固有の使用法ドメインがあります(this明示的にバインドする必要がある場合、または明示的にバインドしない必要がある場合)。これらのドメイン間では、どちらの表記でも十分なケースが多数あります。

ES6の矢印関数には、少なくとも2つの制限があります。

  • 動作しませんnewし、作成時に使用することはできませんprototype
  • this初期化時にスコープに固定された

これらの2つの制限は別として、矢印関数は理論的にはほとんどどこでも通常の関数を置き換えることができます。実際にそれらを使用する正しいアプローチは何ですか?矢印関数を使用する必要があります。例:

  • 「どこでも機能する」、つまりどこでも関数はthis変数にとらわれる必要がなく、オブジェクトを作成していません。
  • 「必要なところすべて」、つまり特定のスコープにバインドする必要があるイベントリスナー、タイムアウトのみ
  • 「短い」関数ではあるが「長い」関数ではない
  • 別の矢印関数を含まない関数のみ

私が探しているのは、ECMAScriptの将来のバージョンで適切な関数表記を選択するためのガイドラインです。ガイドラインは、チーム内の開発者に教えることができるように明確にする必要があり、関数の表記法間で一定のリファクタリングが必要にならないように一貫している必要があります。


33
あなたFixed this bound to scope at initialisationは制限として考えますか?
thefourtheye

12
これは利点ですが、元のコンテキストの外で関数を再利用することを計画している場合にも、制限になる可能性があります。たとえば、Object.prototypeを介してクラスに関数を動的に追加する場合。「制限」とは、値を変更thisすることは、通常の関数では実行できるが、矢印関数では実行できないことです。
リコーニング

1
正直なところ、コーディングスタイルのガイドラインはかなり説得力があると思います。誤解しないでください。それらは重要だと思いますが、すべての人に適した単一のガイドラインはありません。
Felix Kling 2014

Fixed this bound to scope at initialisation制限ではないと思います。:)この記事を
ご覧

3
@thefourtheye、ここでの「制限」とは、「ダムの自動コード変換プログラムが盲目的に1つを別のものに置き換え、すべてが期待どおりに実行されると想定できないための制限」を意味します。
Pacerier

回答:


322

少し前に、私たちのチームはすべてのコード(中型のAngularJSアプリ)をTraceur Babelを使用してコンパイルされたJavaScriptに移行しました。現在、ES6以降の関数には次の経験則を使用しています。

  • functionグローバルスコープおよびObject.prototypeプロパティに使用します。
  • classオブジェクトコンストラクターに使用します。
  • =>他のあらゆる場所で使用してください。

アロー関数をほとんどどこでも使用するのはなぜですか?

  1. スコープの安全性:矢印関数を一貫して使用すると、すべてがthisObjectルートと同じように使用されることが保証されます。単一の標準関数のコールバックでさえも、多数の矢印関数と混在していると、スコープがめちゃくちゃになる可能性があります。
  2. コンパクト:アロー関数は読み書きが簡単です。(これは意外に思われるかもしれないので、さらにいくつかの例を挙げます)。
  3. 明快さ:ほとんどすべてがアロー関数であるとき、どんなレギュラーもfunctionすぐにスコープを定義するために突き出します。開発者は常に次の上位のfunctionステートメントを調べて、それthisObjectが何であるかを確認できます。

グローバルスコープまたはモジュールスコープで常に通常の関数を使用する理由

  1. にアクセスしてはならない機能を示しますthisObject
  2. windowオブジェクト(グローバルスコープ)は、最高が明示的に対処しています。
  3. 多くのObject.prototype定義はグローバルスコープ内にあり(考えてみてくださいString.prototype.truncate)、定義はfunctionとにかくタイプでなければなりません。functionグローバルスコープで一貫して使用すると、エラーを回避できます。
  4. グローバルスコープの多くの関数は、古いスタイルのクラス定義のオブジェクトコンストラクターです。
  5. 関数には名前を付けることができます1。これには次の2つの利点があります。(1)特に他の関数呼び出しの外側で書くfunction foo(){}よりも書きにくいですconst foo = () => {}。(2)関数名がスタックトレースに表示されます。すべての内部コールバックに名前を付けるのは面倒ですが、すべてのパブリック関数に名前を付けることは、おそらく良い考えです。
  6. 関数宣言は巻き上げられます(宣言する前にアクセスできることを意味します)。これは、静的ユーティリティ関数の便利な属性です。


オブジェクトコンストラクター

アロー関数をインスタンス化しようとすると、例外がスローされます。

var x = () => {};
new x(); // TypeError: x is not a constructor

したがって、矢印関数に対する関数の主な利点の1つは、関数がオブジェクトコンストラクタとしても機能することです。

function Person(name) {
    this.name = name;
}

ただし、機能的に同一の2 ES Harmony ドラフトクラス定義は、ほぼ同じくらいコンパクトです。

class Person {
    constructor(name) {
        this.name = name;
    }
}

以前の表記法の使用は最終的には推奨されなくなると思います。オブジェクトコンストラクター表記は、オブジェクトがプログラムで生成される単純な匿名オブジェクトファクトリーでも一部で使用される場合がありますが、それ以外の場合は使用できません。

オブジェクトコンストラクタが必要な場合は、class上記のように関数をに変換することを検討する必要があります。構文は匿名の関数/クラスでも機能します。


矢印関数の読みやすさ

通常の関数に固執するためのおそらく最良の議論-スコープの安全性は非難される-矢印関数は通常の関数よりも読みにくいということでしょう。そもそもコードが機能していない場合、矢印関数は必要ないように見えるかもしれません。また、矢印関数が一貫して使用されていない場合、醜く見えます。

ECMAScriptの5.1は私たちに、機能を与えたので、ECMAScriptのはかなり変化しているArray.forEachArray.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、クラスメソッドまたはクラスである必要があるすべてのキーワードを予約することをお勧めします。


ノート

  1. 名前付き矢印関数は、ES6仕様で延期れました。将来のバージョンで追加される可能性があります。
  2. ドラフト仕様によれば、クラスがキーワードを使用しない限り、「クラス宣言/式は、関数宣言の場合とまったく同じようにコンストラクター関数/プロトタイプのペアを作成します」extend。小さな違いは、クラス宣言は定数であるのに対し、関数宣言は定数ではないということです。
  3. 単一ステートメントの矢印関数のブロックに関する注意:副作用(たとえば、代入)のためだけに矢印関数が呼び出される場合はいつでも、ブロックを使用するのが好きです。そうすれば、戻り値を破棄できることは明らかです。

4
あなたが使いたいと思う別の時functionは、あなたthisが縛られたくないときですよね?この最も一般的なシナリオthisはイベントです。イベントをトリガーしたオブジェクト(通常はDOMノード)を参照できます。
ブレット

13
実際に例3では、通常の関数の方が読みやすいと思います。プログラマー以外でも、何が起こっているのかを理解することができます。矢印を使用して、その例を理解するには、矢印がどのように機能するかを正確に知る必要があります。たぶんもっと多くの改行が矢印の例を助けるでしょうが、私は知りません。私の2セントだけですが、矢印は私をうんざりさせます(しかし、私はまだそれらを使用していないので、すぐに改宗する可能性があります。)
スペンサー

3
フェアポイントである@スペンサー。私自身の経験から、=>時間の経過とともに見栄えがよくなる。プログラマーではない人が2つの例について非常に異なって感じるとは思えません。ES2016のコードを記述している場合、通常はこの多くの矢印関数を使用することにもなりません。この例では、async / awaitと配列内包表記を使用すると、reduce()呼び出しに含まれる矢印関数は1つだけになります。
2015年

3
その例では、通常の関数の方がはるかに読みやすいとSpencerに完全に同意します。
jonschlinkert

2
良い答えです、thx!個人的には、可能な限りグローバルスコープの矢印も使用します。これにより、「機能」がほとんどなくなります。私にとってコード内の「関数」とは、特別なケースを意味し、突出して慎重に検討する必要があります。
kofifus 2017

80

提案によると、矢は「伝統的ないくつかの一般的な問題点に取り組み、解決すること」を目的としたFunction Expression。彼らはthis語彙的にバインドし、簡潔な構文を提供することで問題を改善することを意図していました。

しかしながら、

  • this語彙的に一貫してバインドすることはできません
  • アロー関数の構文は繊細で曖昧です

したがって、矢印関数は混乱とエラーの機会を生み出し、JavaScriptプログラマーの語彙から除外し、function排他的に置き換える必要があります。

語彙について this

this 問題があります:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

アロー関数はthis、コールバック内のプロパティにアクセスする必要がある問題を修正することを目的としています。これを行う方法はすでにいくつかあります。this変数に割り当てる、を使用するbind、またはArray集約メソッドで使用可能な3番目の引数を使用することができます。しかし、矢印が最も簡単な回避策であると思われるため、メソッドは次のようにリファクタリングできます。

this.pages.forEach(page => page.draw(this.settings));

ただし、コードがjQueryなどのメソッドをthis特別にバインドするライブラリを使用しているかどうかを検討してください。ここで、this処理する2つの値があります。

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

を動的にバインドするfunctionために使用する必要があります。ここではアロー関数は使用できません。eachthis

複数のthis値を処理することも混乱を招く可能性があります。これはthis、どの作者が話しているのかを知ることが難しいためです。

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

著者は実際に電話するつもりでしたBook.prototype.reformatか?それとも彼は拘束するのを忘れthisて電話をかけるつもりReader.prototype.reformatでしたか?ハンドラーをarrow関数に変更すると、作成者がdynamicを必要としているかどうかは同様に疑問にthis思われますが、1行に収まるため矢印を選択します。

function Reader() {
    this.book.on('change', () => this.reformat());
}

「矢印がときどき間違った関数になるのは例外ですか?動的なthis値がほとんど必要ない場合は、ほとんどの場合、矢印を使用しても問題ありません。」

しかし、これを自問してみてください。「コードをデバッグして、エラーの結果が「エッジケース」によって引き起こされたことがわかるのは 『価値がある』でしょうか?」ほとんどの場合だけでなく、トラブルを回避したいのですが、時間の100%。

より良い方法があります:常に使用しfunctionthis常に動的にバインドできるため)、常にthis変数を介して参照します。変数は字句であり、多くの名前を想定しています。変数に割り当てるthisと、意図が明確になります。

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

さらに、常にthis変数に割り当てることにより(this関数が1つしかない場合や他の関数がない場合でも)、コードを変更した後でも意図が明確に保たれます。

また、ダイナミックthisは例外的ではありません。jQueryは5,000万を超えるWebサイトで使用されています(この記事の執筆時点で2016年2月)。this動的にバインドする他のAPIは次のとおりです。

  • Mocha(昨日ダウンロード数1万2千回)は、を介してテスト用のメソッドを公開していますthis
  • Grunt(昨日〜63,000ダウンロード)は、ビルドタスクのメソッドを公開していthisます。
  • バックボーン(昨日〜22,000ダウンロード)はにアクセスするメソッドを定義していますthis
  • イベントAPI(DOMのような)はEventTargetwithを参照しthisます。
  • パッチまたは拡張されたプロトタイプAPIは、を使用したインスタンスを参照しますthis

http://trends.builtwith.com/javascript/jQueryおよびhttps://www.npmjs.comを介した統計。)

thisすでに動的バインディングが必要になる可能性があります。

語彙thisが期待される場合とそうでない場合があります。ダイナミックthisが期待されるのと同じですが、そうでない場合もあります。ありがたいことに、期待されるバインディングを常に生成して伝達するより良い方法があります。

簡潔な構文について

アロー関数は、関数の「より短い構文形式」を提供することに成功しました。しかし、これらの短い関数はあなたをより成功させるでしょうか?

あるx => x * xよりも「読みやすいですか」function (x) { return x * x; }?たぶんそうなのです。なぜなら、単一の短いコード行を生成する可能性が高いからです。Dysonによると、画面からの読み取りの効率に対する読み取り速度と行の長さの影響

中程度の行の長さ(1行あたり55文字)は、通常の速度と速い速度で効果的な読み取りをサポートするようです。これにより、最高レベルの理解が得られました。。。

条件付き(3項)演算子と単一行ifステートメントについても、同様の正当化が行われます。

しかし、あなたは本当に提案で宣伝されている単純な数学関数を書いていますか?私のドメインは数学ではないので、私のサブルーチンがめったにエレガントではありません。むしろ、ダイソンの定義による「読みやすさ」を無効にするエディターまたはスタイルガイドが原因で、矢印関数が列の制限を超えて別の行に折り返されることがよくあります。

「可能であれば、短い関数に短いバージョンを使用するのはどうですか?」しかし、今や文体規則は言語の制約と矛盾します。「可能な限り最短の表記法のみを使用するとthis、期待どおりにバインドされる場合があることを覚えておいてください。」このような融合により、矢印は特に誤用されやすくなります。

アロー関数の構文には多くの問題があります。

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

これらの関数はどちらも構文的に有効です。しかしdoSomethingElse(x);、の本体にはありませんb。インデントが不十分で、トップレベルのステートメントにすぎません。

ブロックフォームに展開すると、暗黙のはなくなり、return復元を忘れることがあります。しかし、この式は副作用を生み出すことのみを目的としたものである可能性があるので、明示returnが今後必要になるかどうか誰が知っていますか?

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

レストパラメータとして意図されているものは、spread演算子として解析できます。

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

割り当ては、デフォルトの引数と混同される可能性があります。

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens

ブロックはオブジェクトのように見えます:

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

これは何を意味するのでしょうか?

() => {}

作者は何もしない、または空のオブジェクトを返す関数を作成するつもりでしたか?(これを念頭に置いて、{後に配置する=>必要がありますか?式の構文のみに制限する必要がありますか?これにより、矢印の頻度がさらに減少します。)

=>以下のように見える<=>=

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

アロー関数式をすぐに呼び出す()には、外側に配置する必要()がありますが、内側に配置することは有効であり、意図的である可能性があります。

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

ただし、(() => doSomething()());すぐに呼び出される関数式を書くつもりで書いたとしても、何も起こりません。

上記のすべてのケースを念頭に置いて、矢印関数が「より理解しやすい」と主張することは困難です。一つは、可能性があり、この構文を利用するために必要なすべての特別なルールを学びます。それは本当に価値がありますか?

の構文functionは例外なく一般化されています。function排他的に使用するということは、言語自体が混乱を招くコードを書くことを防ぐことを意味します。すべてのケースで構文的に理解されるべき手続きを書くために、私はを選びますfunction

ガイドラインについて

「明確」かつ「一貫性がある」必要があるガイドラインを要求します。矢印関数を使用すると、最終的には構文的に有効で論理的に無効なコードが生成され、両方の関数形式が意味的かつ任意に絡み合います。したがって、私は以下を提供します:

ES6の関数表記のガイドライン:

  • 常にでプロシージャを作成しますfunction
  • 常にthis変数に割り当てます。使用しないでください() => {}

5
JavaScriptに関する関数型プログラマーの見解について興味深い記事を書いてください。私がプライベート変数の引数に同意するかどうかはわかりません。IMO本当に必要な人はほとんどいません。そうする人はおそらく他の契約機能も必要とし、とにかくTypeScriptのような言語拡張に行くでしょう。私はselfこれの代わりにの魅力を確かに見ることができます。あなたが述べた矢の関数の落とし穴もすべて有効であり、中括弧なしで行くことができる他のステートメントと同じ標準がここにも間違いなく適用されます。そうでなければ、私はあなたの議論で、どこでも矢印関数を主張できると思います。
15年

7
「物事を行う複数の方法があると、職場や言語コミュニティでの議論や議論の不必要なベクトルが作成されます。言語文法で不適切な選択ができなかった方がいいでしょう。」とても同意します。素敵な記事!アロー関数は実際には一歩後退していると思います。別のトピックでは、同僚が一連の.prototype定義を使用してJavaScriptをC#に変換しようとするのをやめたいと思います。嫌な感じです。私はあなたの投稿を匿名でリンクする必要があります:)
Spencer

11
とてもよく書かれています!私はあなたのほとんどの点に同意しませんが、反対の視点を考慮することは重要です。
minexew 2016年

4
アロー関数ではなく、奇妙な振る舞いthisがJavascriptの問題です。暗黙的にバインドされる代わりにthis、明示的な引数として渡されるべきです。
bob 2016年

5
" 常に関数を使用し(常に動的にバインドできるため)、常に変数を介してこれを参照します。 " 私はこれ以上反対することができませんでした!

48

矢印関数scope、関数を単純化し、thisキーワードをより単純にすることで解決するために作成されました。=>矢印のような構文を使用します。

注:既存の機能に代わるものではありません。すべての関数構文を矢印関数に置き換えると、すべてのケースで機能するわけではありません。

既存のES5構文を見てみましょう。thisキーワードがオブジェクトのメソッド(オブジェクトに属する関数)内にある場合、それは何を参照しますか?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

上記のスニペットはを参照objectし、名前を出力し"RajiniKanth"ます。以下のスニペットを調べて、これがここで何を指摘するかを見てみましょう。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

では、thisキーワードが内部にある場合はmethod’s functionどうでしょうか。

ここでは、これはから落ちwindow objectたものinner functionとして参照しscopeます。なぜならthis、この場合、関数の所有者を常に参照しているからです。この場合は、スコープの外にあるため、ウィンドウ/グローバルオブジェクトです。

objectのメソッド内にある場合— functionの所有者がオブジェクトです。したがって、thisキーワードはオブジェクトにバインドされます。それでも、それが関数の内部にある場合、スタンドアロンまたは別のメソッド内では、常にwindow/globalオブジェクトを参照します。

var fn = function(){
  alert(this);
}

fn(); // [object Window]

この問題を私たちES5自身で解決する方法はいくつかあります。ES6の矢印関数の詳細を説明する前に、その解決方法を見てみましょう。

通常は、メソッドの内部関数の外部に変数を作成します。これで、‘forEach’メソッドthisobject’sプロパティとその値にアクセスできるようになりました。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

を使用bindthisて、メソッドを参照するキーワードをにアタッチしmethod’s inner functionます。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   }.bind(this));
  }
};

Actor.showMovies();

今でES6矢印機能、我々は対処することができますlexical scoping簡単な方法で問題。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Arrow functionsbindthisがであることを除いて、関数ステートメントに似ていますparent scope。場合はarrow function is in top scopethis引数が参照されますwindow/global scope、通常の関数内の矢印機能ながら、その外側の関数としてのこの引数は同じになります。

arrow関数を使用するthisscope、作成時に囲みにバインドされ、変更できません。新しい演算子bind、call、applyはこれには影響しません。

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

上記の例では、これの制御を失っています。上記の例は、の変数参照thisまたはを使用して解決できますbind。ES6を使用するとthis、のバインド先としてを簡単に管理できlexical scopingます。

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

Arrow関数を使用しない場合

オブジェクトリテラルの内部。

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getName矢印関数で定義されていますが、ため、呼び出し時に、それは未定義の警告this.nameであるundefinedコンテキストがに残るようwindow

これは、矢印関数がコンテキストを語彙的にwindow object...外部スコープにバインドするために発生します。実行this.namewindow.name未定義のと同等です。

オブジェクトのプロトタイプ

にメソッドを定義する場合も同じ規則が適用されますprototype object。代わりに、誤っをもたらすsayCatName方法を、定義するための矢印機能を使用しますcontext window

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

コンストラクターの呼び出し

this構築呼び出しでは、新しく作成されたオブジェクトです。new Fn()を実行すると、のコンテキストはconstructor Fn新しいオブジェクトですthis instanceof Fn === true

this 囲んでいるコンテキスト、つまり、新しく作成されたオブジェクトに割り当てられないようにする外部スコープからセットアップされます。

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

動的コンテキストによるコールバック

アロー関数はcontext宣言時に静的にバインドし、動的にすることはできません。イベントリスナーをDOM要素にアタッチすることは、クライアント側のプログラミングでは一般的なタスクです。イベントは、これをターゲット要素としてハンドラー関数をトリガーします。

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

thisグローバルコンテキストで定義されるアロー関数のウィンドウです。クリックイベントが発生すると、ブラウザーはボタンコンテキストでハンドラー関数を呼び出そうとしますが、矢印関数は事前定義されたコンテキストを変更しません。this.innerHTMLと同等window.innerHTMLであり、意味がありません。

ターゲット要素に応じてこれを変更できる関数式を適用する必要があります。

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

ユーザーがボタンをクリックすると、ハンドラー関数ではこれがボタンになります。したがってthis.innerHTML = 'Clicked button'、ボタンのテキストを正しく変更して、クリックされたステータスを反映します。

参照:https : //dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/


まあ、私は認めなければなりません、「最高は真ん中にある」。ステートメントに賛成です。その矢印関数は、考えられる関数の使用例をカバーしていません。彼らは本当に一般的な問題の一部だけを解決するように設計されています。完全にそれらに切り替えるだけでやり過ぎになります。
BlitZ

@DmitriPavlutin:更新された投稿をチェックしてください。たくさんのものが集まっています...参照を投稿する必要があるかもしれません。
Thalaivar

2
「bindを使用して、メソッドを参照するthisキーワードをメソッドの内部関数にアタッチする」行の後のコード。バグがあります。残りの例をテストしましたか?
Isaac Pak

1は、using bind to attach the this keyword that refers to the method to the method’s inner function.構文エラーがあります。
Coda Chang

あるべきvar Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], showMovies: function() { this.movies.forEach(function(movie){ alert(this.name + ' has acted in ' + movie); }.bind(this)) } }; Actor.showMovies();
コーダチャン

14

矢印関数-これまでに最も広く使用されているES6機能...

使用法:次のシナリオを除き、すべてのES5関数をES6矢印関数に置き換える必要があります。

矢印関数は使用しないでください:

  1. 機能巻き上げがしたいとき
    • 矢印関数は匿名であるため。
  2. 関数で this/ を使用したい場合arguments
    • 矢印関数は独自のthis/ argumentsを持たないため、外部コンテキストに依存します。
  3. 名前付き関数を使いたいとき
    • 矢印関数は匿名であるため。
  4. 関数をaとして使用する場合 constructor
    • 矢印関数には独自の関数がないためthisです。
  5. オブジェクトリテラルのプロパティとして関数を追加し、その中でオブジェクトを使用する場合
    • アクセスできないのでthis(オブジェクト自体である必要があります)。

よりよく理解するために、矢印関数のバリアントのいくつかを理解しましょう:

バリエーション1:関数に複数の引数を渡して、その関数から値を返したい場合。

ES5バージョン

var multiply = function (a,b) {
    return a*b;
};
console.log(multiply(5,6)); //30

ES6バージョン

var multiplyArrow = (a,b) => a*b;
console.log(multiplyArrow(5,6)); //30

注: functionキーワードは必須ではありません。 =>必要とされている。 {}提供しない場合は{} returnJavaScriptによって暗黙的に追加され、提供{}するreturn場合は必要に応じて追加する必要があります。

バリエーション2:関数に引数を1つだけ渡して、そこから値を返したい場合。

ES5バージョン

var double = function(a) {
    return a*2;
};
console.log(double(2)); //4

ES6バージョン

var doubleArrow  = a => a*2;
console.log(doubleArrow(2)); //4

注:引数を1つだけ渡す場合は、括弧を省略できます()

バリアント3:関数に引数を渡したくない場合、値を返したくない場合。

ES5バージョン

var sayHello = function() {
    console.log("Hello");
};
sayHello(); //Hello

ES6バージョン

var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); //sayHelloArrow

バリエーション4:アロー関数から明示的に戻りたい場合。

ES6バージョン

var increment = x => {
  return x + 1;
};
console.log(increment(1)); //2

バリアント5:アロー関数からオブジェクトを返したい場合。

ES6バージョン

var returnObject = () => ({a:5});
console.log(returnObject());

注:オブジェクトを括弧()で囲む必要があります。そうしないと、JavaScriptがブロックとオブジェクトを区別できません。

バリエーション6:アロー関数にはarguments、の外部コンテキストに依存する独自の(オブジェクトのような配列)はありませんarguments

ES6バージョン

function foo() {
  var abc = i => arguments[0];
  console.log(abc(1));
};    
foo(2); // 2

注: fooES5機能と、あるargumentsオブジェクトのような配列と、それに渡された引数がある2のでarguments[0]ためにfoo2です。

abcそれはargumentsそれ自身を持たないのでES6アロー関数であり、それゆえ代わりにそれarguments[0]fooその外部コンテキストの出力をします。

バリアント7:アロー関数にはthis独自の機能はなく、外部コンテキストに依存しますthis

ES5バージョン

var obj5 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" here is undefined.
        });
     }
};

obj5.greetUser("Katty"); //undefined: Katty

注:setTimeoutに渡されるコールバックはES5関数でありthisuse-strict環境内で定義されていない独自のコールバックがあるため、出力を取得します。

undefined: Katty

ES6バージョン

var obj6 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user)); 
      // this here refers to outer context
   }
};

obj6.greetUser("Katty"); //Hi, Welcome: Katty

注:に渡されたコールバックはsetTimeout機能を矢印とそれが自分のだがないES6でthisそれは外側の文脈だから、それはそれを取るように、greetUser持っているthisものがありobj6、我々は出力を取得し、したがって:

Hi, Welcome: Katty

その他:new矢印関数で は使用できません。矢印関数にはprototypeプロパティがありません。or thisを通じてarrow関数が呼び出されたときのバインディングはありません。applycall


6

これまでのすばらしい答えに加えて、矢印関数が特定の意味で「通常の」JavaScript関数よりも根本的に優れているという非常に異なる理由を提示したいと思います。議論のために、TypeScriptやFacebookの "Flow"などのタイプチェッカーを一時的に使用するとします。有効なECMAScript 6コードとフロータイプアノテーションである次のおもちゃモジュールを考えてみてください(この回答の最後に、実際にはBabelから生じる型なしコードを含めて、実際に実行できるようにします)。

export class C {
  n : number;
  f1: number => number; 
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

次のように、別のモジュールのクラスCを使用するとどうなるかを確認します。

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

ご覧のとおり、タイプチェッカーはここで失敗しました。f2は数値を返すはずでしたが、文字列を返しました!

さらに悪いことに、f2の「this」がf2の引数リストに出現せず、「this」に必要な型を追加できないため、考えられる型チェッカーは通常の(矢印ではない)JavaScript関数を処理できないようです。 f2への注釈として。

この問題は、タイプチェッカーを使用しない人にも影響しますか?静的型がない場合でも、そこにあるかのように考えるからです。(「最初のパラメーターは数値でなければならず、2番目のパラメーターは文字列である必要があります。」など)関数の本体で使用される場合とされない場合がある非表示の「this」引数は、メンタルブックキーピングを困難にします。

以下は、Babelによって生成される実行可能な型なしバージョンです。

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!



3

このスレッドの最初の回答で書いたすべてのことは、今でも待機しています。しかし、コードのスタイルについての私の意見はそれ以来発展してきたので、私の最後の質問に基づいて、この質問に対する新しい答えがあります。

語彙について this

私の最後の回答では、この言語についての根底にある信念は、私が行っていた議論とは直接関係がなかったため、意図的に避けました。それにもかかわらず、これが明示的に述べられていなければ、多くの人々が矢印が非常に有用であるとわかったときに、なぜ私の矢印を使わないように勧めているのか理解できます。

私の信念はこれです:thisそもそも使用すべきではありません。したがって、人が意図的にthisコードでの使用を避けた場合、this矢印の「字句」機能はほとんどまたはまったく価値がありません。また、this悪いことを前提として、arrowの扱いはthis「良いこと」ではありません。その代わり、それは別の悪い言語機能のための一種のダメージコントロールです。

これは一部の人には発生しないと思いますが、発生する人でも、thisファイルごとに100回表示されるコードベース内で常に作業している必要があり、少し(または多く)の損傷制御がすべてです。合理的な人が期待することができます。つまり、矢印は、ある状況において、悪い状況をより良くするときに良いものになる可能性があります。

this矢印を使用するよりも使用する方が簡単な場合でも、矢印の使用規則は非常に複雑です(現在のスレッドを参照)。したがって、あなたが要求したように、ガイドラインは「明確」でも「一貫性」でもありません。プログラマーは矢印のあいまいさを知っていても、とにかく肩をすくめて受け入れてしまうと思いthisます。字句の価値がそれらを覆い隠してしまうからです。

これはすべて、次の実現の前置きです。を使用しない場合thisthis通常はその矢印に関する曖昧さが原因でなくなります。この文脈では、矢はより中立になります。

簡潔な構文について

私が最初の回答を書いたとき、私は、ベストプラクティスへの従順な従順であっても、より完璧なコードを生成できるなら、支払う価値のある代償であるという意見でした。しかし、私は結局、簡潔さがコードの品質を向上させることができる抽象化の形としても機能できることに気づきました。ベストプラクティスから逸脱することを正当化するのに十分です。

つまり、くそったれ、ワンライナー関数も欲しいのです!

ガイドラインについて

this-neutral arrow関数の可能性と、簡潔さを追求する価値があるので、以下のより寛大なガイドラインを提供します。

ES6の関数表記のガイドライン:

  • 使用しないでくださいthis
  • 名前で呼び出す関数には関数宣言を使用します(関数が巻き上げられるため)。
  • コールバックには矢印関数を使用します(より簡潔になる傾向があるため)。

下部にある「ES6の関数表記のガイドライン」セクションに100%同意します。特に、巻き上げとインラインのコールバック関数については同意します。いい答えだ!
Jeff McCloud

1

簡単な方法で、

var a =20; function a(){this.a=10; console.log(a);} 
//20, since the context here is window.

別のインスタンス:

var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();

回答:コンソールは20を出力します。

関数は、この例では、独自のスタックが作成され、実行されるたびにいる理由ex機能がで実行されるnewコンテキストが作成されるので、オペレータ、およびときinnerにそれを実行しているJSは、新しいスタックを作成して実行するinner機能aをglobal contextありますが、ローカルコンテキスト。

したがって、inner関数にローカルコンテキストを持たせたい場合はex、そのコンテキストを内部関数にバインドする必要があります。

矢印はこの問題を解決しますが、存在する場合はGlobal contextそれらを使用しlocal contextます。ではgiven example,それがかかりますnew ex()ようthis

したがって、バインディングが明示的であるすべてのケースで、Arrowsはデフォルトで問題を解決します。


1

矢印関数またはラムダは、ES 6で導入されました。最小限の構文におけるそのエレガントさを除いて、最も顕著な機能の違いは 、矢印関数内のスコープです。this

では通常の関数表現、thisキーワードはに基づいて異なる値にバインドされている文脈コールが行われました。

では矢印機能thisされた字句それはオーバー閉じ意味し、バインドthis矢印関数は(親スコープ)で定義された範囲から、と関係なく、それが呼び出されたか/と呼ばれる変更されません。

オブジェクトのメソッドとしての矢印関数

// this = global Window
let objA = {
 id: 10,
 name: "Simar",
 print () { // same as print: function() 
  console.log(`[${this.id} -> ${this.name}]`);
 }
}
objA.print(); // logs: [10 -> Simar]
objA = {
 id: 10,
 name: "Simar",
 print: () => {
  // closes over this lexically (global Window)
  console.log(`[${this.id} -> ${this.name}]`);
 }
};
objA.print(); // logs: [undefined -> undefined]

以下の場合、objA.print()ときprint()の方法は、定期的に使用して定義されfunction 、それが解決して働いていたthisに適切にobjAメソッド呼び出しのためではなく、矢印のように定義するとき失敗した=>機能。これはthis、通常の関数では、オブジェクト(objA)のメソッドとして呼び出されたときに、それ自体がオブジェクトであるためです。ただし、アロー関数の場合は、それが定義された囲みスコープ(この場合はグローバル/ウィンドウ)にthis字句的にバインドさthisれ、のメソッドとしての呼び出し中も同じままobjAです。

オブジェクトのメソッドの通常の関数に対する矢印関数の利点はthis、時間定義で修正およびバインドされることが予想される場合のみです。

/* this = global | Window (enclosing scope) */

let objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( function() {
    // invoked async, not bound to objB
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( () => {
    // closes over bind to this from objB.print()
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [20 -> Paul]

以下の場合、方法は、関数のように定義された呼び出した[$ {this.id} - > {this.name}] 非同期のコールバックなど 、を正しく解決矢印関数がコールバックとして使用された場合には失敗しましたコールバックが通常の関数として定義されたとき。矢印だからである関数が渡さオーバー閉じすなわちその親から字句。それを定義した呼び出し。言い換えると、の呼び出しはそれ自体であったため、矢印関数はtoにバインドされて、それとしてバインドされます。objB.print()print()console.log()setTimeoutthisobjB=>setTimeout(()=>..)thisobjB.print()=>setTimeout(()==>...objBthisobjB.print() thisobjB

を簡単に使用Function.prototype.bind()して、正しいにバインドすることにより、コールバックを通常の関数として機能させることができますthis

const objB = {
 id: 20,
 name: "Singh",
 print () { // same as print: function() 
  setTimeout( (function() {
    console.log(`[${this.id} -> ${this.name}]`);
  }).bind(this), 1)
 }
}
objB.print() // logs: [20 -> Singh]

ただし、アロー関数は便利で、非同期コールバックの場合にエラーが発生しにくくなります。非同期コールバックの場合this、関数の定義時にそれが取得され、バインドされるはずです。

これを呼び出し全体で変更する必要があるアロー関数の制限

いつでも、this呼び出し時に変更できる関数が必要です。矢印関数は使用できません。

/* this = global | Window (enclosing scope) */

function print() { 
   console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
 id: 10,
 name: "Simar",
 print // same as print: print
};
obj.print(); // logs: [10 -> Simar]
const obj2 = {
 id: 20,
 name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]

矢印機能付き上記の意志作業なしconst print = () => { console.log([$ {this.id} - > {this.name}] );}のようにthis変更することはできませんとに結合したままであろうthis、それが定義された囲み範囲(グローバル/ウィンドウ)の。これらのすべての例では、異なるオブジェクト(obj1およびobj2)を使用して同じ関数を次々と呼び出しましたが、どちらもprint()関数の宣言後に作成されました。

これらは人為的な例でしたが、実際の例をいくつか考えてみましょう。で機能するreduce()メソッドと同様のメソッドを記述arrays する必要があるthis場合、呼び出しコンテキストから推測する必要があるため、ラムダとして定義することはできません。呼び出された配列

このため、コンストラクター関数は宣言時に設定できないため、constructor関数を矢印関数として定義thisすることはできません。コンストラクター関数がnewキーワードで呼び出されるたびに、新しいオブジェクトが作成され、その特定の呼び出しにバインドされます。

また、フレームワークまたはシステムが後で動的コンテキストthis で呼び出されるコールバック関数を受け入れる場合、this呼び出しごとに変更する必要がある可能性があるため、矢印関数を使用できません。この状況は通常、DOMイベントハンドラーで発生します。

'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

これは、Angular 2+Vue.jsなどのフレームワークで、テンプレートコンポーネントのバインディングメソッドが通常の関数/メソッドであると想定している理由でもありthisます。これらの呼び出しは、バインディング関数のフレームワークによって管理されるためです。(AngularはZone.jsを使用して、ビューテンプレートバインディング関数の呼び出しの非同期コンテキストを管理します)。

一方、Reactでは、コンポーネントのメソッドをイベントハンドラーとして渡す場合、たとえば、すべての呼び出しについて矢印関数として<input onChange={this.handleOnchange} />定義する必要があります。handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}これは、レンダリング用のJSXを生成したコンポーネントの同じインスタンスにする必要があります。 DOM要素。


この記事は、私の媒体出版物にも掲載されています。Artileが好きな場合、またはコメントや提案がある場合は、[ Medium]で拍手するコメントを残してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.