こんにちはみなさん
ES6って、正式名称がES2015だったんですね。
そんな話もあったなぁとか思いながら、適用していなかったので、タイトルから変えてみました。
新年から早速ですが、ES2015から追加された仕様としてarrow関数なるものがあるので、そいつについて調べてみました。
arrow 関数の書き方
arrow関数は基本的には無名関数の省略記法です。
基本形
基本形は以下のとおりです
(param1, param2, ...) => {
//処理内容
}
引数の個数はいくつでも問題ないし、なくても構いません。
例として以下のコードを見てください
'use strict'
//普通の無名関数の書き方
const scount = function(n, p) {
for (let i = 1; i < n; i++) {
console.log(Math.pow(i, p));
}
}
//arrow 関数を使った無名関数の書き方
const scount2 = (n, p) => {
for(let i = 1; i < n; i++) {
console.log(Math.pow(i, p));
}
}
scount(10, 3);//1, 8, 27, 64, ...
scount2(10, 3);//1, 8, 27, 64, ...
このように、scountとscount2が全く同じ動きをしてくれます。
省略形
引数が一つの時
引数が一つだけの時は「()」を省略できるようです
param => { //処理内容 }
ちょっと不思議ですが、次のように書けます
const scount3 = n => {
console.log(n * n);
}
scount3(11);//121
逆に引数が一つもない場合はカッコは省略できません
//const scount4 = => {// この書き方は無理
const scount4 = () => {
console.log('yeah!!');
}
scount4();
ステートメントが一つだけの場合
処理内容のステートメントが一つだけであれば、波括弧「{}」を省略できます。
また、波括弧省略時はステートメントの結果が返り値になるので、return が不要になります
const scount5 = n => n * n;
console.log(scount5(12));// 144
ただし、オブジェクトリテラルを返すときは、波括弧がオブジェクトを表すのか処理内容のブロックを表すのかわからなくなるので、ステートメントの方に「()」をつけて書きます
const scount6 = n => ({first: n, secont: n * n});
console.log(scount6(13));
コールバックでの使用例
このarrow関数を使うと、すっきりと書ける場合があります
例えば、配列の中から偶数のもののみ抜き出すようにフィルタリングしてみましょう。
const arr = [1,5,7,3,4,6,13,10];
//普通に書いた場合
const arr1 = arr.filter(function (n) {
return n % 2 == 0;
});
//arrow関数を使った場合
const arr2 = arr.filter(n => n % 2 == 0);
console.log(arr1);// [ 4, 6, 10 ]
console.log(arr2);// [ 4, 6, 10 ]
なんとなく直感的にかけるようになったかなぁとか思います。
arrow関数の中のthis
実は、arrow関数は単純な関数の書き換えではありません。
というのも、arrow関数は「this」が何かを明確化します。
ややこしいので以下の例を見てみましょう
thisは何を表す?
JavaScript最大の脅威は、この「this」かもしれません。
私のようなPHPerにはかなり厳しいものでした。
以下の様なコードを考えてみましょう。
//コンストラクタ
function scount7() {
this.count = 0;
setInterval(function() {
this.count++;
}, 100)
}
const counter1 = new scount7;
setTimeout(() => console.log(counter1.count, 3000);// 30前後を期待したい
これは何を期待したかったかというと、scount7という名前のオブジェクトを作ると、そのオブジェクトは100msごとに内部のカウンタが増加していくため、3000ms後に中身を覗くと、countが大体30くらいになっていて欲しい、ということです。
ところが、実際にはこいつは0のままです。
これは、「this」というものがブロック内のスコープではなく、呼び出し元を示していることに由来しています。
コールバックで呼びだされた時、この関数はグローバルオブジェクトに呼び出されていることになっているようで、「this」はcounter1ではなくグローバルオブジェクトになってしまっているため、counter1.countはびくともしていないということらしいです。
やりたいことを実現するのであれば、以下のように書かなければなりません
function scount7() {
this.count = 0;
let self = this;//オブジェクトのエイリアス
setInterval(function() {
self.count++;//thisだと呼び出し元に変化するため、selfにする
}, 100)
}
const counter1 = new scount7;
setTimeout(() => console.log(counter1.count), 3000);
これで目的を達成することができます。
arrow 演算子における「this」
javascript忍者によると、このthisを呼び出し元コンテキストというそうですが、なんというかわかりにくいです。
はじめ同僚がvar self = this
というコードを書いていて、何でこんなの書いてんの?って思ったほどです。
で、arrow関数だと、この「this」がレキシカル(字句解析した時点での内容)に束縛することができます。つまり、以下のように書いて、問題なく期待通りの結果を受け取ることができます。
function scount8() {
this.count = 0;
setInterval(() => this.count++, 100)
}
const counter2 = new scount8;
setTimeout(() => console.log(counter2.count), 3000);//30前後になる(多分29)
例えsetIntervalによってコールバックでarrow関数が呼ばれても、arrow関数の「this」は変わらずscount8 (counter2) を表しています。
この「レキシカル」という性質は、ようは字句解析した(読み込んだ)時点で、その内容を固定します。
なので、例えばcall
のように、明確に呼び出し元を規定するメソッドを使用しても、thisの内容は相変わらずそのスコープ内のthisになります
function scount9() {
this.count = 1;
this.addn = num => {
const f = i => i + this.count;
const o = {count: 3};
return f.call(o, num)//呼び出し元コンテキストをscount9オブジェクトではなくoに変える
}
}
const counter3 = new scount9;
console.log(counter3.addn(3));// 6ではなく、4になる
こんな感じです。
つまり、arrow関数の内部でのthisは普通のthisではなく、var self = this
をした後のself
と同じものであるということです。
まとめ
arrow関数は書き方も簡単ですが、何よりthisをレキシカルに束縛できる所が良いと思います。
中にはダイナミックにthisが変わったほうがいいと思う方もいるかもしれませんが、私のようなPHPやPythonくらいしかオブジェクト触っていないJS素人にとっては、いちいち呼び出し元気にするくらいなら、はじめからthisが固定されている方が理解しやすいです
実際MDNでも「ダイナミックにthisが変わることがじれったい」と表現していました。
ES2015が本格的に投入されれば、私のような人間にもJavaScriptでサーバーもフロントもかけるじゃないかなぁとか思ってたりします。
Koaとかよさ気なライブラリもあるので
今回はこんなところで失礼します
参考
アロー関数 - JavaScript | MDN
アロー関数が実装された - JS.next
ES6時代のNode.js