LoginSignup
6
7

More than 5 years have passed since last update.

ラブアローファンクション!

Posted at

みんなのコード打ち抜くぞ~♡(ごあいさつ)

ES2015で使えるようになったアロー関数がなかなか使い勝手がよいです。
現時点ではWebブラウザだと対応状況がイマイチですがNode.jsなどサーバサイドJSで使う分にはES2015が使えるバージョンであれば思う存分使えるので、もはやアロー関数なしには生きていけなさそうです。
まさにラブアローファンクション。

Node v4.3.1で検証

アロー関数とは

アロー関数式(またはファットアロー関数)はfunction式に比べより短い構文を持ち、 this の値をレキシカルに束縛します。アロー関数は常に匿名関数です。

なんのこっちゃ?って感じですね。

とりあえず書いてみよう

従来の関数定義

var hoge = function(args) {
  console.log(args);
}
hoge('fuga');
result
fuga

アロー関数定義

'use strict';

const hoge = (args) => {
  console.log(args)
}
hoge('piyo');
result
piyo

はい、これだけです。
だからどうした?!そう思うでしょう。自分もはじめはそうでした。

アロー関数を使う最大のメリットだと思われる「thisの値をレキシカルに束縛する」振る舞いを実例を交えながら便利さをひも解いていきたいと思います。

thisの値をレキシカルに束縛する?

JavaScriptは無名関数やクロージャを用いると呼び出し元と関数内でthisが指すものが変わってしまい、想定していた振る舞いと違う動きをすることがあります。
これは無名関数やクロージャなど関数内でのthisの値が暗黙的にglobalオブジェクトを指すようになってしまう仕様によるものらしいのです。
さらに、厳格モード(strict mode)では関数内でのthisがundefinedなってしまいます。
参考:
https://msdn.microsoft.com/ja-jp/library/br230269(v=vs.94).aspx
http://qiita.com/takeharu/items/9935ce476a17d6258e27
http://qiita.com/Hiraku/items/d249a2f2f13532748324

アロー関数内でのthisは呼び出し元のthisと同じものを指すように束縛する(=thisの値をレキシカルに束縛する)という特徴があります。

それではコードを書いてみよう

コードを書く前に…

余談ですがES2015になってクラスを定義することができるようになりました。
といっても今までできていたクラスっぽい振る舞いをする関数定義の糖衣構文なだけらしいですが…。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes

今回はJavaScriptの新しいクラス定義を用いて例を作成していきます。thisもいっぱいでてきますし。

無名関数を用いてthisの動きを確認するためにあえてPromiseを使っています。
Promiseである意味は特に意味はありません!
forEachとかで用いる無名関数とかでも同じです。

ダメな例

'use strict';

class Character {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  getNamePromise() {
    console.log(this); // <- ここでのthisはCharacterのインスタンス
    return (new Promise(function(resolve){
      console.log(this); // <- ここでのthisはundefinedとなる
      let name= this.getName(); // <- TypeError: Cannot read property 'getName' of undefined
      resolve(name);
    }));
  }
}

const chara = new Character('umi');
console.log('getName', chara.getName());  // <- 'getName umi'
chara.getNamePromise()
.then(function(name){
  console.log('getNamePromise', name);
})
.catch(function(err){
  console.log('error', err);
});
result
getName umi
Character { name: 'umi' }
undefined
error [TypeError: Cannot read property 'getName' of undefined]

上記の通りPromiseの無名関数内のthisがundefinedになってしまいますね。

回避例1:一時変数に退避させて参照する

これを回避するためによく用いるのが一時変数に退避させて参照する手です。

'use strict';

class Character {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  getNamePromise() {
    let _self = this; // <- 一時変数に退避する
    return (new Promise(function(resolve){
      let name= _self.getName();
      resolve(name);
    }));
  }
}

const chara = new Character('umi');
console.log('getName', chara.getName());  // <- 'getName umi'
chara.getNamePromise()
.then(function(name){
  console.log('getNamePromise', name); // <- 'getNamePromise umi'
})
.catch(function(err){
  console.log('error', err);
});
result
getName umi
getNamePromise umi

初めてこの書き方を見た時は目から鱗でしたが、今見るとこの書き方すごくダサい…

回避例2:bindで無名関数のthisを束縛

ほかにもFunction.prototype.bindでthisを束縛するという手もありますね。

'use strict';

class Character {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  getNamePromise() {
    console.log(this); // <- ここでのthisはCharacterのインスタンス
    return (new Promise(
      (function(resolve){
        console.log(this); // <- bindによりthisはCharacterのインスタンスとなる
        let name= this.getName(); 
        resolve(name);
      }).bind(this) // <- bindで無名関数のthisをCharacterのインスタンスに束縛する
    ));
  }
}

const chara = new Character('umi');
console.log('getName', chara.getName());  // <- 'getName umi'
chara.getNamePromise()
.then(function(name){
  console.log('getNamePromise', name); // <- 'getNamePromise umi'
})
.catch(function(err){
  console.log('error', err);
});
result
getName umi
Character { name: 'umi' }
Character { name: 'umi' }
getNamePromise umi

まぁ普通はこんな書き方しませんが…
不便さを感じてもらうために極端に書いてるものだと思ってください!

ここでアロー関数の登場

いよいよアロー関数の登場です。
上記のダメな例のコードの関数定義をアロー関数に書き換えます。

'use strict';

class Character {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  getNamePromise() {
    console.log(this); // <- thisはCharacterのインスタンス
    return (new Promise((resolve) => {
      console.log(this); // <- thisはCharacterのインスタンス
      let name = this.getName();
      resolve(name);
    }));
  }
}

const chara = new Character('umi');
console.log('getName', chara.getName());  // <- 'getName umi'
chara.getNamePromise()
.then(function(name){
  console.log('getNamePromise', name); // <- 'getNamePromise umi'
})
.catch(function(err){
  console.log('error', err);
});
result
getName umi
Character { name: 'umi' }
Character { name: 'umi' }
getNamePromise umi

ね、簡単でしょ?

おまけ

無名関数をクラスメソッド定義に置換すると一見アクセスできそうだけどダメなんだなー!
クラスメソッドのthisを束縛するにはbindを用いて呼び出すしかなさそう…?
もっとスマートな書き方があったら教えてください。

無名関数をクラスメソッドに置換

NG例
'use strict';

class Character {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  getNamePromise() {
    console.log(this); // <- thisはCharacterのインスタンス
    return (new Promise(this._promiseFunc));
  }
  _promiseFunc(resolve) {
    console.log(this); // <- thisはundefined
    resolve(this.getName()); // <- TypeError: Cannot read property 'getName' of undefined
  }
}

const chara = new Character('umi');
console.log('getName', chara.getName());  // <- 'getName umi'
chara.getNamePromise()
.then(function(name){
  console.log('getNamePromise', name);
})
.catch(function(err){
  console.log('error', err);
});
result
getName umi
Character { name: 'umi' }
undefined
error [TypeError: Cannot read property 'getName' of undefined]

bindする

OK例
'use strict';

class Character {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  getNamePromise() {
    return (new Promise(this._promiseFunc.bind(this))); // <- bindでthisをCharacterのインスタンスに束縛する
  }
  _promiseFunc(resolve) {
    console.log(this); // <- thisはCharacterのインスタンス
    resolve(this.getName());
  }
}

const chara = new Character('umi');
console.log('getName', chara.getName());  // <- 'getName umi'
chara.getNamePromise()
.then(function(name){
  console.log('getNamePromise', name); // <- 'getNamePromise umi'
})
.catch(function(err){
  console.log('error', err);
});
result
getName umi
Character { name: 'umi' }
getNamePromise umi
6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7