Javascriptでアロー関数を使用する方法
アロー関数の構文はECMAScript6で導入されました。この新しい構文を使用することで、場合によっては(すべてではありませんが)より簡潔で読みやすいコードを生成できます。特に、関数が1つの式のみを含む場合に有効です。このチュートリアルでは、アロー関数をどのように定義するか、標準関数との違い、アロー関数の使用が適切でないケースについて学びます。
このチュートリアルでは、あなたは学びます:
- アロー関数とは何ですか。
- アロー関数がどのように定義されるか。
- アロー関数と標準関数の違い。
- アロー関数が使用できないケース。
「アロー関数」とは何ですか?
アロー関数はECMAScript6で導入されました。この新しい構文を使用することで、より簡潔なコードを得ることができ、多くの場合、マルチラインのコールバックを1行に翻訳することができます。これは、暗黙の返り値のような機能のおかげです。しかし、その特異性のために、アロー関数は標準関数のすべての場面で置き換えることはできません。使用できない文脈もあるため、その理由を見ていきます。
標準関数からアロー関数へ
この段落では、標準の関数をアロー関数で置き換える方法の例を見ていきます。高階関数のコールバックを使用することは、このような置き換えを行うのが完全に問題ない場合の完璧な例です。
ご存知のように、高次関数とは、別の関数を返す関数、または別の関数を引数として受け取る関数のことです。この例では、filter、またはお好きならarray.prototype.filterを使用します。このarray objectのメソッドは、引数として関数を取り、元の配列のすべての要素のうち、コールバック関数内で実装されたテストに合格した要素で構成された新しい配列を返します。
フィルターをクラシックな関数とともに使用する例を見てみましょう。「ロード・オブ・ザ・リング」の本からキャラクターを表すオブジェクトの配列があると想像してみてください。
const characters = [
{ name: 'Frodo', race: 'Hobbit' },
{ name: 'Sam', race: 'Hobbit' },
{ name: 'Legolas', race: 'Elf' },
{ name: 'Aragorn', race: 'Man' },
{ name: 'Boromir', race: 'Man' }
]
このcharacters配列には5つの要素が含まれており、それぞれに2つのプロパティ、nameとraceがあります。さて、今、私たちが人間の種族に属するキャラクターのみで構成された新しい配列を作成したいとしましょう。filterと標準関数の構文を使用して、次のように書きます:
const men = characters.filter(function filterMen(element) {
return element.race == 'Man';
});
前述のように、filterは引数として関数を取ります: 標準の構文を使用する場合、この関数は名前付きでも無名でもかまいません。ほとんどの状況では、無名関数がコールバックとして使用されますが、この例のため、また後で標準関数とアロー関数の構文の違いの一つを強調するために、私たちは関数に名前を付けました: filterMen.
filter に使用するコールバック関数は、元の配列の各要素を処理する際に使用される唯一の 必須 パラメータを取ります。この関数が true を返すと、その要素は新しい配列のメンバーとして挿入され、false を返すと、その要素は挿入されません。この特定のケースでは、簡単なテストを定義しました:
character.race == 'Man'このテストは、処理中の要素のraceプロパティが文字列‘Man’と等しい場合にtrueを返します。上で書いた結果は次のとおりです:
[ { name: 'Aragorn', race: ''Man' },
{ name: 'Boromir', race: ''Man' } ]
それでは、上記のコードをアロー関数を使ってリファクタリングしたいとしましょう。その場合、次のように書きます:
const men = characters.filter(element => element.race == 'Man');
「アロー関数」の構文を使用することで、前回の例と同じ結果をたった1行のコードで達成することができました:これは素晴らしいことではありませんか?最初は新しい構文に混乱するかもしれませんが、心配しないで、そのまま読み続けてください。
アロー関数の構文
標準関数は function キーワードを使用して定義されますが、アロー関数は => シンボルを使用して定義されます。これは、明らかに二つの間の唯一の違いではありません。ここで強調すべき最も重要な違いの一つは、従来の関数は関数式の中で名前付きまたは匿名のどちらかであることができるのに対し、アロー関数は常に匿名であるということです。
アロー関数での引数の定義
前の例では、function キーワードを取り除いたため、最初に読めるのは element です。これは、アロー関数が受け入れる引数です。アロー関数が期待する引数を定義する際のルールはシンプルです:関数が複数の引数を受け入れる場合や、引数が全くない場合は、引数を括弧で囲む必要があります;関数が1つの引数のみを含む場合、今回の例のように、括弧を完全に省略することができます。
例として、2つの数値を引数として受け取り、その積を返す関数を定義したいと考えてみてください。その場合、次のように書きます:
// Since the function takes two parameters, we must use parenthesis
const multiply = (a,b) => a * b;
暗黙の戻り値と波括弧
上記のすべての例では、もう一つのものの欠如に気づいたかもしれません。それは関数の本体を区切る 波括弧 です。なぜそれを省略したのでしょうか?矢印関数の本体が1つの式だけで構成されている場合、波括弧を省略することができます。そうである場合、式の結果は暗黙的に返されます。
// If we omit curly braces the result of the expression is implicitly returned
const multiply = (a,b) => a * b;
multiply(2,3);
6 // Result is 6: it is implicitly returned
// If we use curly braces, the result is not implicitly returned
const multiply = (a,b) => { a * b }
multiply(2,3);
undefined // Result will be undefined, since we didn't explicitly return the result of the expression
上記のコードでは、非常にシンプルな関数multiplyを定義しました。この関数は二つのパラメータを期待しているので、それらを括弧で囲む必要があります。=>記号はアロー関数を定義します。最初の例では、一つの表現しかないため、渡された二つの数の積を返すため、波括弧を省略し、暗黙の戻り値機能を活用することができます。
2番目の例では波括弧を使用したため、関数はundefinedを返しました。暗黙の返り値がないためです。期待される結果を得るには、returnを明示的に使用する必要があります。
関数の本体内の複数の文または表現
波括弧は、矢印関数内で複数の文や式を指定する唯一の方法でもあります。例えば、2つの数の積を返すのではなく、関数が文字列を出力し、それを表示するようにしたい場合を考えてみましょう:
const multiply = (a,b) => {
const product = a*b;
console.log(`The product of ${a} and ${b} is ${product}`);
}
multiply(2,3);
The product of 2 and 3 is 6
もし私たちのアロー関数が中括弧で区切られたオブジェクトリテラルを返さなければならない場合、オブジェクトリテラルを括弧で囲む必要があります。
const createChar = (characterName, characterRace) => ({ name: characterName, race: characterRace });
createChar('Gimli', 'dwarf')
{ name: ''Gimli', race: ''dwarf' }
アロー関数内でthisがどのように動作するか
クラシック関数とアロー関数の最も関連性のある、いや、最も関連性のある違いの1つは、this の動作です。この違いがあるため、場合によってはアロー関数を使用できない理由の主な要因となります。違いを強調する前に、標準関数で this がどのように動作するかをおさらいしましょう。まず覚えておくべきことは、this の値は関数自体がどのように呼び出されるかによって決まるということです。いくつかの例を見てみましょう。
デフォルト:これはグローバルスコープへの参照です。
スタンドアロン関数内で this が使用されている場合、strict mode でないとグローバルスコープを参照し、ブラウザ環境では window オブジェクト、Node.js では global object を指します。同じ状況で strict mode の場合、this は undefined となり、エラーが発生します:
var i = 20; // Here we used var instead of let because the latter doesn't create a property on the global scope.
function foo() {
console.log(this.i);
}
// Non-strict mode
foo()
20
// Strict mode
foo()
TypeError: Cannot read property 'i' of undefined
暗黙的バインディング
標準関数がオブジェクト内で参照され、その関数がそのオブジェクトを context として呼び出されると、ドット表記を使用すると、this はそのオブジェクトへの参照になります。これを私たちは 暗黙のバインディング と呼びます。
function foo() {
console.log(this.i);
}
let object = {
i: 20,
foo: foo // The foo property is a reference to the foo function
}
object.foo() // this is a reference to object, so this.i is object.i
20
明示的バインディング
thisが何を参照すべきかを明示的に宣言している場合、私たちは明示的なバインディングを使用していると言います。これは、関数のcall、apply、またはbindメソッドを使用することで実現できます(JavaScriptでは、関数そのものが第一級オブジェクトです)。上記で最初に言及した場合を思い出してください。デフォルトのバインディングが適用されるときです:
var i = 20;
function foo() {
console.log(this.i);
}
const object = {
i: 100
}
foo() // This will output 20 or generate a TypeError in strict mode.
// If we explicitly set this to be a reference to object the things changes.
// call and apply execute the function immediately with the new context:
foo.call(object) // Output is 100
foo.apply(object) // Output is 100
// bind instead, returns a new function with the specified context.
let boundFoo = foo.bind(object)
boundFoo() // Output is 100
「call」、「apply」および「bind」にはいくつかの違いがあります。重要なのは、後者は指定されたコンテキストにバインドされた「新しい関数」を返すのに対し、他の2つでは指定されたコンテキストにバインドされた関数が即座に実行されるということです。他にも違いはありますが、ここではそれについては見ません。重要なのは、明示的なバインディングの働き方を理解することです。
矢印関数はthisに関してどのように異なるのですか?
上記のすべてのケースと例では、標準関数を使用する際に、thisの値が関数の呼び出し方によって異なることを見ました。しかし、アロー関数はレキシカルthisを使用します:それらは独自のthisを持たず、常にその囲まれたスコープからのthisを使用します。このことが予期しない影響を引き起こす典型的な例は、イベントリスナーにあります。たとえば、「button1」というIDを持つボタンがあり、それがクリックされたときにそのテキストを変更したいとします:
<button id="button1" type="button">Click me!</button>
// The event listener with a standard function as a callback
document.getElementById('button1').addEventListener('click', function() {
this.innerText = "Clicked!";
})
そのコードは完璧に機能し、ボタンがクリックされると、そのテキストは予想通りに変わります。この場合、アロー関数を使用したらどうでしょうか?次のように書くとします:
document.getElementById('button1').addEventListener('click', () => this.innerText = "Clicked!"; )
上記のコードは動作しません、なぜですか?簡単です。最初の例では、標準のコールバック関数内のthisはイベントが発生するオブジェクト(ボタン)を参照しますが、アロー関数を使用する場合、thisは親スコープから継承され、今回はwindowオブジェクトになります。完全性のために言うと、上記の例はアロー関数で動作するように簡単に修正することができます。
document.getElementById('button1').addEventListener('click', event => event.target.innerText = "Clicked!"; )
今回はコードがうまくいきました。なぜなら、ボタンを参照するために this を使わず、関数が受け取る引数として event を許可したからです。関数の本体では event.target を使って、イベントを発生させたオブジェクトを参照しました。
上記の理由と同様に、アロー関数はオブジェクトメソッドやプロトタイプメソッドとして使用できません:
// Arrow functions don't work as object methods...
const object1 = {
i: 1000,
foo: () => console.log(`the value of i is ${this.i}`)
}
object1.foo()
the value of i is undefined
// ...and they don't not work as prototype methods.
const Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.introduce = () => console.log(`My name is ${this.name} and I am ${this.age} years old`);
const jack = new Person('Jack', 100);
jack.name
'Jack'
jack.age
100
jack.introduce()
My name is undefined and I am undefined years old
結論
アロー関数の構文は、ECMAScript6で導入された非常に素晴らしい機能です。この新しい関数定義の方法を使うことで、より短く、よりクリーンなコードを書くことができます。私たちはアロー関数の定義方法と、新しい構文がどのように機能するかを見ました。
私たちはまた、アロー関数がすべての状況で標準関数に取って代わることができない理由も見ました。それは、アロー関数が独自の this を持たず、囲むスコープのものを使用するためです。このため、アロー関数はメソッドやコンストラクタとして使用できないことが、このチュートリアルで見た通りです。もし他のJavaScriptのチュートリアルに興味があるなら、ぜひ見ていてください。次のチュートリアルでは fetch 関数について話します。その間に、プロミスに関する私たちの記事をチェックすることができます。
