web-dev-qa-db-ja.com

ECMAScript 6でArrow関数を使用するのはいつですか?

この質問は、今後のECMAScript 6(Harmony)の文脈でコードスタイルについて考え、すでに言語を扱っている人に向けられています。

() => {}function () {}を使うと、ES6で関数を書くのに非常によく似た2つの方法が得られます。他の言語ではラムダ関数はしばしば匿名であることによって彼ら自身を区別します、しかしECMAScriptではどんな関数でも匿名になることができます。 2つのタイプのそれぞれには、固有の使用法ドメインがあります(つまり、thisを明示的にバインドする必要がある場合、または明示的にバインドしない場合)。これらのドメインの間には、どちらの表記法でも成り立つ膨大な数のケースがあります。

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

  • newでは動作しません
  • 初期化時にthisがスコープにバインドされていたのを修正

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

  • "どこでもそれらが機能する"、すなわちどこでも関数がthis変数を認識する必要はなく、我々はオブジェクトを作成していません。
  • イベントリスナー、タイムアウトなど、特定のスコープにバインドする必要があるのは、「どこでも必要な場所」だけです。
  • 「短い」関数ではなく「長い」関数では
  • 他のarrow関数を含まない関数でのみ

私が探しているのは、ECMAScriptの将来のバージョンで適切な関数表記を選択するためのガイドラインです。ガイドラインは、チーム内の開発者に教えることができるように明確にする必要があり、一貫性を保つようにして、ある関数表記から別の関数表記に頻繁に行ったり来たりすることを必要としません。

365
lyschoening

少し前に、私たちのチームはそのコード(中サイズのAngularJSアプリ)をすべてコンパイルしたJavaScriptに移行しました。 Traceur バベル 。私は現在、ES6以降の機能に次の経験則を使用しています。

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

なぜほとんどどこでも矢印関数を使うのですか?

  1. スコープの安全性:矢印関数が一貫して使用される場合、すべてのものがルートと同じthisObjectを使用することが保証されます。 1つの標準関数コールバックでさえ、たくさんの矢印関数と混在していると、スコープがめちゃくちゃになる可能性があります。
  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に変換することを検討してください。構文は無名関数/クラスでも同様に機能します。


矢印機能の読みやすさ

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

ECMAScript 5.1から機能的なArray.forEachArray.map、およびforループが以前に使用されていたであろう機能を使用しているこれらすべての機能的プログラミング機能が提供されてから、ECMAScriptはかなり変更されました。非同期の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));
}

いずれかのarrow関数は標準関数に置き換えることができますが、そうすることで得られるものはほとんどありません。どちらのバージョンが読みやすいですか?私は最初のものを言うでしょう。

私は、矢印機能を使うのか、それとも通常の機能を使うのかという質問は、時間が経てばあまり意味がなくなると思います。ほとんどの関数は、functionキーワードを廃止するクラスメソッドになるか、またはクラスになります。 Object.prototypeを通じてクラスにパッチを適用するための関数は引き続き使用されます。その間に、私はfunctionキーワードを本当にクラスメソッドやクラスであるべきもののために予約することをお勧めします。


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

提案 によれば、矢印は「従来の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));

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

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

functioneachを動的にバインドするには、thisを使用する必要があります。ここでは矢印関数を使用できません。

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

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

著者は実際にBook.prototype.reformatを呼び出すつもりでしたか?または、thisをバインドするのを忘れて、Reader.prototype.reformatを呼び出すつもりでしたか?ハンドラーを矢印関数に変更すると、作成者が動的なthisを望んでいるのに、1行に収まるために矢印を選択したかどうかも同様に疑問に思うでしょう。

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

「矢印が使用するのに間違った関数になることは例外的ですか?おそらく、動的なthis値がほとんど必要ない場合、ほとんどの場合矢印を使用しても大丈夫でしょう」

しかし、次のことを自問してください:「コードをデバッグし、エラーの結果が「エッジケース」によってもたらされたことを見つけるのは「価値があるだろうか」」ほとんどの場合だけでなく、トラブルを回避したいのですが、 100%の時間。

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

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

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

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

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

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

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

字句thisが期待される場合もありますが、そうでない場合もあります。動的なthisが期待される場合もありますが、そうでない場合もあります。ありがたいことに、期待されるバインディングを常に生成して伝達するより良い方法があります。

簡潔な構文について

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

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

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

条件付き(三項)演算子、および単一行のifステートメントに対しても同様の正当化が行われます。

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

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

矢印関数の構文には多くの問題があります。

const a = x =>
    doSomething(x);

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

これらの関数は両方とも構文的に有効です。しかし、doSomethingElse(x);bの本体にはなく、インデントが不十分なトップレベルのステートメントです。

ブロック形式に展開すると、暗黙のreturnはなくなり、復元するのを忘れることがあります。しかし、式はonlyが副作用を生成することを意図していたので、明示的な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);
};

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

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()());を作成しても、何も起こりません。

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

functionの構文は例外なく一般化されています。 functionを排他的に使用するということは、言語自体が混乱を招くコードの記述を防ぐことを意味します。すべての場合に構文的に理解される必要があるプロシージャを作成するには、functionを選択します。

ガイドラインについて

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

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

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

矢印関数 は、関数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の内側にある場合はどうでしょうか。

ここでは、これはscopeから外れたので、window objectよりinner functionを参照します。 thisは、常にその関数が含まれている関数の所有者を参照しているため、この場合は(範囲外になっているため)ウィンドウ/グローバルオブジェクトです。

それがobjectメソッドの中にある場合 - functionの所有者はオブジェクトです。したがって、thisキーワードはオブジェクトにバインドされています。それでも、それが関数の中にあるとき、単独であるいは他のメソッドの中にあるとき、それは常にwindow/globalオブジェクトを参照するでしょう。

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

fn(); // [object Window]

私たちのES5自体にこの問題を解決する方法があります、それをどのように解決するかについてES6の矢印関数に飛び込む前に、それを調べてみましょう。

通常は、メソッドの内部関数の外側に変数を作成します。これで‘forEach’メソッドはthis、そしてobject’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();

メソッドを参照するbindキーワードをmethod’s inner functionに付加するためにthisを使用します。

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 arrow関数を使えば、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 functionsは、これをparent scopebindする以外は、関数ステートメントに似ています。 arrow function is in top scopethis引数がwindow/global scopeを参照する場合、通常の関数内の矢印関数はそのthis引数をその外側の関数と同じにします。

arrow関数を使用すると、thisは作成時にそれを含むscopeにバインドされ、変更することはできません。 new演算子、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では、thislexical 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

しないときは矢印機能

オブジェクトリテラルの中

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

Actor.getName();

Actor.getNameはarrow関数で定義されていますが、コンテキストがundefinedのままであるためthis.namewindowであるため、呼び出し時に未定義のアラートが出されます。

これは、arrow関数がコンテキストをwindow object...すなわち外部スコープと字句的に結び付けるために起こります。 this.nameを実行することは、未定義のwindow.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.innerHTMLwindow.innerHTMLと同等であり、意味がありません。

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

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

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

参考文献: https://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/

32
Thalaivar

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

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

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

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

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

Variant 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キーワードは必須ではありません。 =>が必要です。 {}はオプションです。提供しない場合は{}returnはJavaScriptによって暗黙的に追加され、提供する場合は{}は必要に応じてreturnを追加する必要があります。それ。

Variant 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つだけ渡す場合、括弧()を省略できます。

Variant:引数を関数に渡したくなく、値を返したくない場合。

ES5バージョン

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

ES6バージョン

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

Variant 4:矢印関数から明示的に戻りたい場合。

ES6バージョン

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

Variant 5:矢印関数からオブジェクトを返したい場合。

ES6バージョン

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

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

Variant 6:矢印関数は、argumentsの外部コンテキストに依存する独自のarguments(オブジェクトのような配列)を持ちません。

ES6バージョン

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

注:fooはES5関数であり、オブジェクトのようなarguments配列とそれに渡される引数は2であるため、fooarguments[0]は2です。

abcは、独自のargumentsを持たないため、ES6の矢印関数です。したがって、arguments[0] of fooを出力します。代わりに外部コンテキストです。

Variant 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関数であり、use-strict環境では未定義の独自のthisを持っているため、出力が得られます。

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プロパティがありません。 thisまたはapplyを介して矢印関数が呼び出された場合、callのバインディングはありません。

14
Manishz90

これまでのすばらしい答えに加えて、ある意味で矢印関数が「普通の」JavaScript関数よりも根本的に優れているという非常に異なる理由を示したいと思います。説明のために、一時的にTypeScriptやFacebookの "Flow"のような型チェッカーを使うと仮定しましょう。有効なECMAScript 6コードとFlow型の注釈である次のtoyモジュールを考えてみましょう。(この回答の最後に、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は数値を返すはずでしたが、文字列を返しました。

さらに悪いことに、考えられない型チェッカーは普通の(矢印ではない)JavaScript関数を扱うことができないようです。 f2の引数リストなので、 "this"に必要な型はおそらく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!
6

矢印関数 は独自のthis、arguments、super、またはnew.target をバインドしないため、ローカルthisへのアクセスが不要な場合は常に矢印関数を使用することをお勧めします。

3
zowers

簡単な方法では、

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();

Ans:コンソールは20を印刷します。

その理由は、関数が実行されるたびに独自のスタックが作成されるため、この例ではexname__関数がnewname__演算子で実行され、innername__が実行されるとJSが新しいスタックを作成してinnername__関数を実行するためです。ローカルコンテキストがありますがglobal context

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

矢印はGlobal contextを取る代わりにこの問題を解決し、存在する場合はlocal contextを取ります。 given example,では、new ex()thisname__として使用します。

したがって、束縛が明示的であるすべての場合において、矢印はデフォルトで問題を解決します。

ES 6で矢印関数またはLambdasが導入されました。最小限の構文の優雅さを除けば、最も注目すべき機能的の違いは、矢印関数内のthisのスコープです。

正規関数式では、thisキーワードは、それが呼び出されるコンテキストに基づいてさまざまな値にバインドされます。

矢印関数では、this字句的にバインドされています。つまり、矢印関数が定義されたスコープからthisを閉じます(parent-また、どこでどのように呼び出されたり呼び出されたりしても変わりません。

制限Arrow - オブジェクトのメソッドとしての機能

// 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()メソッドが通常のfunctionを使用して定義されているときのprint()の場合、メソッド呼び出しに対してthisobjAに正しく解決することで機能しましたが、arrow=>関数として定義されている場合は失敗しました。これは、通常の関数内のthisがオブジェクトのメソッド(objA)として呼び出されたときは、それ自体がオブジェクトだからです。しかし、arrow関数の場合、thisは、それが定義されているスコープ(この場合はglobal/Window)の囲んでいるスコープのthisにレキシカルにバインドされ、objAのメソッドとして呼び出されても変わりません。

thisが時間定義時に固定&バインドされることが予想される場合に限り、オブジェクトのメソッド内の通常の関数に対するarrow-functionsの利点は無効です。

/* 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]

objB.print()メソッドがsetTimeoutへのコールバックとして非同期にconsole.log( [$ {this.id}] )を呼び出す関数として定義されているprint()の場合、thisobjBに正しく解決されました。コールバックとして使用されますが、コールバックが通常の関数として定義されていると失敗しました。これは、setTimeout(()=>..)に渡されたarrow =>関数が、その親から辞書的にthisを閉じたためです。それを定義したobjB.print()の呼び出し。つまり、setTimeout(()==>...objBのin呼び出しはそれ自体thisであったため、arrow =>関数は、そのthisとしてobjBにバインドされたobjB.print()に渡されました。

通常の関数として定義されたコールバックを正しいthisにバインドすることで、Function.prototype.bind()を簡単に使用できます。

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がわかっています。

呼び出し間でこれを変更する必要がある場合のArrow-Functionsの制限

呼び出し時に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]

thisは矢印関数const print = () => { console.log( [$ {this.id} - > {this.name}] );}で変更することはできず、定義されているスコープのthisにバインドされたままになります(global)/窓)。これらすべての例で、異なるオブジェクト(obj1obj2)を使用して同じ関数を次々に呼び出しました。どちらもprint()関数が宣言された後に作成されました。

これらは人為的な例ですが、実際の例をもう少し考えてみましょう。 arraysで動作するものと同じようにreduce()メソッドを作成する必要がある場合は、呼び出しコンテキストから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としてのfunction/methodsは、バインディング関数のためのフレームワークによって管理されます。 (Angularは、view-templateバインディング関数の呼び出しのための非同期コンテキストを管理するためにZone.jsを使用します)。

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


この記事は私の の出版物にも掲載されています。あなたがそのartileが好きな、または何かコメントや提案があるならば、拍手または去る )コメントon

1
Simar Singh

私はまだこのスレッドの 私の最初の答え に書いたことすべてを傍観しています。しかし、それ以来、コードスタイルに関する私の意見は発展してきたので、この質問に対する私の最後の質問に基づく新しい答えを得ました。

字句thisnameについて__

私の最後の答えでは、私が行っていた議論とは直接関連していないので、私はこの言語について私が持っている根本的な信念を故意に避けました。それにもかかわらず、これが明示的に述べられていなくても、私は矢印がそれほど有用であると思うとき、なぜ私は矢印を使わないことを私の勧告で単に争うのか理解できます。

私の考えはこれです:私たちは最初からthisname__を使うべきではありません。したがって、ある人が意図的に自分のコードでthisname__の使用を避けた場合、矢印の「字句thisname__」機能はほとんど、あるいはまったく価値がなくなります。また、thisname__が悪いことであるという前提の下では、arrowのthisname__の扱いは「良いこと」ではなく、代わりに別の悪い言語機能のための損害管理の一種です。

私はこれが一部の人には起こらないことを理解します、しかしそれをする人にさえ、彼らはthisname__がファイルごとに100回現れるコードベース内で働くことを常に見つけなければなりません。すべての合理的な人が望むことができます。だから、矢印は悪い状況を良くするとき、ある意味では良いものになるだろう。

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

これはすべて、次の実現への序文です。thisname__を使用しない場合、通常矢印が引き起こすthisname__に関するあいまいさは無関係になります。これに関連して、矢印はより中立になります。

簡潔な文法について

私が最初の答えを書いたとき、たとえそれが私がより完璧なコードを作り出すことができるのであれば、ベストプラクティスへの怠惰な遵守でさえも支払う価値のある代償であると私は考えました。しかし、簡潔さは、コードの品質を向上させる抽象化の形としても役立つことができるようになりました。ベストプラクティスから逸脱することを正当化するのに十分なほどです。

言い換えれば、ダム、ワンライナー機能も欲しいのです!

ガイドラインについて

thisname __-ニュートラルな矢印関数の可能性と、追跡する価値のある簡潔さを考えて、私は次のより寛容なガイドラインを提供します。

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

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