この質問は、今後のECMAScript 6(Harmony)の文脈でコードスタイルについて考え、すでに言語を扱っている人に向けられています。
() => {}
とfunction () {}
を使うと、ES6で関数を書くのに非常によく似た2つの方法が得られます。他の言語ではラムダ関数はしばしば匿名であることによって彼ら自身を区別します、しかしECMAScriptではどんな関数でも匿名になることができます。 2つのタイプのそれぞれには、固有の使用法ドメインがあります(つまり、this
を明示的にバインドする必要がある場合、または明示的にバインドしない場合)。これらのドメインの間には、どちらの表記法でも成り立つ膨大な数のケースがあります。
ES6の矢印機能には、少なくとも2つの制限があります。
new
では動作しませんthis
がスコープにバインドされていたのを修正これら2つの制限はさておき、矢印関数は理論的にはほとんどどこでも通常の関数を置き換えることができます。実際にそれらを使用する正しいアプローチは何ですか?矢印機能を使うべきです。例えば:
this
変数を認識する必要はなく、我々はオブジェクトを作成していません。私が探しているのは、ECMAScriptの将来のバージョンで適切な関数表記を選択するためのガイドラインです。ガイドラインは、チーム内の開発者に教えることができるように明確にする必要があり、一貫性を保つようにして、ある関数表記から別の関数表記に頻繁に行ったり来たりすることを必要としません。
少し前に、私たちのチームはそのコード(中サイズのAngularJSアプリ)をすべてコンパイルしたJavaScriptに移行しました。 Traceur バベル 。私は現在、ES6以降の機能に次の経験則を使用しています。
Object.prototype
プロパティでfunction
を使用します。class
を使用してください。=>
を使用してください。なぜほとんどどこでも矢印関数を使うのですか?
thisObject
を使用することが保証されます。 1つの標準関数コールバックでさえ、たくさんの矢印関数と混在していると、スコープがめちゃくちゃになる可能性があります。function
はスコープを定義するためにすぐに飛び出します。開発者はいつでも次に高いfunction
ステートメントを調べて、thisObject
が何であるかを確認できます。グローバルスコープまたはモジュールスコープで通常の関数を常に使用するのはなぜですか?
thisObject
にアクセスしてはいけない関数を示します。window
オブジェクト(グローバルスコープ)は明示的に扱うのが最善です。Object.prototype
定義はグローバルなスコープ内にあり(String.prototype.truncate
などを考えてください)、それらは一般にとにかくfunction
型でなければなりません。グローバルスコープでfunction
を一貫して使用すると、エラーを回避するのに役立ちます。function foo(){}
よりconst foo = () => {}
を書くのが面倒です - 特に他の関数呼び出しの外で。 (2)関数名がスタックトレースに表示されます。すべての内部コールバックに名前を付けるのは面倒ですが、すべてのパブリック関数に名前を付けるのはおそらく良い考えです。
オブジェクトコンストラクタ
矢印関数をインスタンス化しようとすると、例外がスローされます。
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.forEach
、Array.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
キーワードを本当にクラスメソッドやクラスであるべきもののために予約することをお勧めします。
注
extend
キーワードを使用しない限り、「クラス宣言/式は、関数宣言の場合とまったく同じようにコンストラクタ関数/プロトタイプのペアを作成します」。多少の違いは、クラス宣言は定数であるのに対して、関数宣言は定数ではないということです。提案 によれば、矢印は「従来の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);
});
};
function
がeach
を動的にバインドするには、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は次のとおりです。
this
を介してテスト用のメソッドを公開しています。this
を介してビルドタスクのメソッドを公開します。this
にアクセスするメソッドを定義します。EventTarget
でthis
を参照します。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文字)は、通常の速度と高速での効果的な読み取りをサポートするようです。これは最高レベルの理解をもたらしました。 。 。
条件付き(三項)演算子、および単一行の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
を選択します。
ガイドラインについて
「明確」で「一貫性のある」必要があるガイドラインを要求します。矢印関数を使用すると、最終的に構文的に有効な論理的に無効なコードが生成され、両方の関数形式が有意義かつ任意に絡み合います。したがって、私は以下を提供します:
function
を使用してプロシージャを作成します。this
を変数に割り当てます。 () => {}
を使用しないでください。矢印関数 は、関数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 scope
にbind
する以外は、関数ステートメントに似ています。 arrow function is in top scope
、this
引数が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では、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
オブジェクトリテラルの中
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
getName: () => {
alert(this.name);
}
};
Actor.getName();
Actor.getName
はarrow関数で定義されていますが、コンテキストがundefined
のままであるためthis.name
はwindow
であるため、呼び出し時に未定義のアラートが出されます。
これは、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.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://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/
矢印関数-これまでで最も広く使用されているES6機能...
使用法:次のシナリオを除き、すべてのES5機能をES6矢印機能に置き換える必要があります。
矢印関数は使用しないでください:
this
/arguments
を使用する場合this
/arguments
を持たないため、外部のコンテキストに依存します。constructor
として使用する場合this
がないためです。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
であるため、foo
のarguments[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
のバインディングはありません。
これまでのすばらしい答えに加えて、ある意味で矢印関数が「普通の」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!
矢印関数 は独自のthis、arguments、super、またはnew.target をバインドしないため、ローカルthis
へのアクセスが不要な場合は常に矢印関数を使用することをお勧めします。
簡単な方法では、
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を印刷します。
その理由は、関数が実行されるたびに独自のスタックが作成されるため、この例ではex
name__関数がnew
name__演算子で実行され、inner
name__が実行されるとJSが新しいスタックを作成してinner
name__関数を実行するためです。ローカルコンテキストがありますがglobal context
。
したがって、inner
name__関数にex
name__というローカルコンテキストを持たせたい場合は、コンテキストを内部関数にバインドする必要があります。
矢印はGlobal context
を取る代わりにこの問題を解決し、存在する場合はlocal context
を取ります。 given example,
では、new ex()
をthis
name__として使用します。
したがって、束縛が明示的であるすべての場合において、矢印はデフォルトで問題を解決します。
ES 6で矢印関数またはLambdasが導入されました。最小限の構文の優雅さを除けば、最も注目すべき機能的の違いは、矢印関数内のthis
のスコープです。
正規関数式では、
this
キーワードは、それが呼び出されるコンテキストに基づいてさまざまな値にバインドされます。矢印関数では、
this
は字句的にバインドされています。つまり、矢印関数が定義されたスコープからthis
を閉じます(parent-また、どこでどのように呼び出されたり呼び出されたりしても変わりません。
// 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()
の場合、メソッド呼び出しに対してthis
をobjA
に正しく解決することで機能しましたが、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()
の場合、this
がobjB
に正しく解決されました。コールバックとして使用されますが、コールバックが通常の関数として定義されていると失敗しました。これは、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
がわかっています。
呼び出し時に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)/窓)。これらすべての例で、異なるオブジェクト(obj1
とobj2
)を使用して同じ関数を次々に呼び出しました。どちらも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 中 。
私はまだこのスレッドの 私の最初の答え に書いたことすべてを傍観しています。しかし、それ以来、コードスタイルに関する私の意見は発展してきたので、この質問に対する私の最後の質問に基づく新しい答えを得ました。
字句this
nameについて__
私の最後の答えでは、私が行っていた議論とは直接関連していないので、私はこの言語について私が持っている根本的な信念を故意に避けました。それにもかかわらず、これが明示的に述べられていなくても、私は矢印がそれほど有用であると思うとき、なぜ私は矢印を使わないことを私の勧告で単に争うのか理解できます。
私の考えはこれです:私たちは最初からthis
name__を使うべきではありません。したがって、ある人が意図的に自分のコードでthis
name__の使用を避けた場合、矢印の「字句this
name__」機能はほとんど、あるいはまったく価値がなくなります。また、this
name__が悪いことであるという前提の下では、arrowのthis
name__の扱いは「良いこと」ではなく、代わりに別の悪い言語機能のための損害管理の一種です。
私はこれが一部の人には起こらないことを理解します、しかしそれをする人にさえ、彼らはthis
name__がファイルごとに100回現れるコードベース内で働くことを常に見つけなければなりません。すべての合理的な人が望むことができます。だから、矢印は悪い状況を良くするとき、ある意味では良いものになるだろう。
this
name__を使用してコードを作成する方が、矢印を使用しない場合よりも簡単な場合でも、矢印を使用するための規則は非常に複雑なままです(「現在のスレッド」を参照)。したがって、ガイドラインはあなたが要求したように「明確」でも「一貫」でもありません。たとえプログラマが矢印のあいまいさについて知っていても、とにかくそれらをすくめて受け入れるのではないかと思います。なぜなら、字句this
name__の値がそれらを覆い隠しているからです。
これはすべて、次の実現への序文です。this
name__を使用しない場合、通常矢印が引き起こすthis
name__に関するあいまいさは無関係になります。これに関連して、矢印はより中立になります。
簡潔な文法について
私が最初の答えを書いたとき、たとえそれが私がより完璧なコードを作り出すことができるのであれば、ベストプラクティスへの怠惰な遵守でさえも支払う価値のある代償であると私は考えました。しかし、簡潔さは、コードの品質を向上させる抽象化の形としても役立つことができるようになりました。ベストプラクティスから逸脱することを正当化するのに十分なほどです。
言い換えれば、ダム、ワンライナー機能も欲しいのです!
ガイドラインについて
this
name __-ニュートラルな矢印関数の可能性と、追跡する価値のある簡潔さを考えて、私は次のより寛容なガイドラインを提供します。
this
name__を使用しないでください。