NodeSchoolのtower-of-babelを解いてみる #1

# はじめに
前回のlearnyounodeでnode.jsについて一通り学んだので、次はES6の文法を学ぶべくtower-of-babelをやってみたので内容を共有します。

- [NodeSchool](http://nodeschool.io/)
- [tower-of-babel](https://github.com/yosuke-furukawa/tower-of-babel)

![image](https://qiita-image-store.s3.amazonaws.com/0/37467/42091066-979c-54c5-9d70-62ab62f45529.png)


# BABEL SETUP
- ES6の構文はブラウザがサポートしていないので、ES6の文法を有効にするためには`babel`を用いる
- `npm install babel-cli -g`で`babel`と`babel-node`をインストール
- コマンドライン引数を受け取ってそれを出力する

```program.js

var arg = process.argv[2];
console.log(`Hello ${arg}`);

```

# CLASS
- 今までJSでクラスライクなものを作る時はコンストラクタとして関数を定義し、`prototype`に対してメソッドを定義してきた

```prototype.js
var Person = function(name) {
      this.name = name;
    };

    Person.prototype.getName = function() {
      return this.name;
    };

    Person.prototype.setName = function(name) {
      this.name = name;
    };

    var alice = new Person("alice");
    alice.getName(); // alice
    alice.setName('bob');
    alice.getName(); // bob

```

- ES6の書き方で書くと下記のようになる。Javaっぽいですね。

```es6.js
class Person {
      constructor(name) {
        this.name = name;
      }
      getName() {
        return this.name;
      }
      setName(name) {
        this.name = name;
      }
    }

    var alice = new Person("alice");
    alice.getName(); // alice
    alice.setName('bob');
    alice.getName(); // bob

```

## 問題
- 上記にならって、下記のクラスをES6のclass構文を使って書き換える

```js
var Character = function(x, y) {
      this.x = x;
      this.y = y;
      this.health_ = 100;
    };

    Character.prototype.damage = function() {
      this.health_ = this.health_ - 10;
    };

    Character.prototype.getHealth = function() {
      return this.health_;
    };

    Character.prototype.toString = function() {
      return "x: " + this.x + " y: " + this.y + " health: " + this.getHealth();
    };
```

```program.js
"use strict";

class Character {
  constructor(x,y) {
    this.x = x;
    this.y = y;
    this.health_ = 100;
  }
  damage() {
    return this.health_ = this.health_ - 10;
  }
  getHealth() {
    return this.health_;
  }
  toString() {
      return "x: " + this.x + " y: " + this.y + " health: " + this.getHealth();
  }
}

var x = process.argv[2];
var y = process.argv[3];
var character = new Character(+x, +y);
character.damage();
console.log(character.toString());

```
- `use strict`を加えないと下記のエラーが出るので書き忘れないようにしましょう。

```
$ babel-node program.js
/Users/01006524/Development/src/localhost/tower-of-babel/class/program.js:1
(function (exports, require, module, __filename, __dirname) { class Character {
                                                              ^^^^^

SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:374:25)
    at loader (/Users/01006524/.nodebrew/node/v5.3.0/lib/node_modules/babel-cli/node_modules/babel-register/lib/node.js:134:5)
    at Object.require.extensions.(anonymous function) [as .js] (/Users/01006524/.nodebrew/node/v5.3.0/lib/node_modules/babel-cli/node_modules/babel-register/lib/node.js:144:7)
    at Module.load (module.js:344:32)
    at Function.Module._load (module.js:301:12)
    at Function.Module.runMain (module.js:430:10)
    at /Users/01006524/.nodebrew/node/v5.3.0/lib/node_modules/babel-cli/lib/_babel-node.js:161:27
    at Object.<anonymous> (/Users/01006524/.nodebrew/node/v5.3.0/lib/node_modules/babel-cli/lib/_babel-node.js:162:7)
    at Module._compile (module.js:398:26)

```

# CLASS EXTEND
- 継承の扱い方を学ぶ。基本的には`extends`を使って継承を行う。親クラスの持つメソッドやメソッドを子クラスから呼び出す場合、`super`というキーワードを利用
  - この辺もJavaと同じですね。

```js

// Characterクラス
    class Character {
      constructor(x, y) {
        this.x = x;
        this.y = y;
        this.health_ = 100;
      }
      attack(character) {
        if (!(character instanceof Character)) throw new Error('');
        character.health_ -= 10;
      }
    }

    // CharacterをMonsterクラスに継承
    class Monster extends Character {
      constructor(x, y, name) {
        super(x, y);
        this.name = name;
      }
      attack(character) {
       // super で親クラスのattackをそのまま呼ぶ
       super.attack(character);
       character.health_ -= 5;
     }
   }
```

## 問題
- 下記のコードをES6のclass構文を用いて書き換える
- `util`モジュールを用いて継承するためには`util.inherits(constructor, superConstructor)`を使う
  - `Player`がサブクラス（子クラス）、`Character`がスーパークラス（親クラス）になるので、そのように書く

```js
var util = require('util');
   var Character = function(x, y) {
     this.x = x;
     this.y = y;
     this.health_ = 100;
   };

   Character.prototype.damage = function() {
     this.health_ = this.health_ - 10;
   };

   Character.prototype.getHealth = function() {
     return this.health_;
   };

   Character.prototype.toString = function() {
     return "x: " + this.x + " y: " + this.y + " health: " + this.getHealth();
   };

   var Player = function(x, y, name) {
     Character.call(this, x, y);
     this.name = name;
   };

   util.inherits(Player, Character);

   Player.prototype.move = function(dx, dy){
     this.x += dx;
     this.y += dy;
   };

   Player.prototype.toString = function() {
     return "name: " + this.name + " " + Player.super_.prototype.toString.call(this);
   };
```

```program.js
var util = require('util');

class Character {
  constructor(x,y) {
    this.x = x;
    this.y = y;
    this.health_ = 100;
  }
  damage() {
    return this.health_ = this.health_ - 10;
  }
  getHealth() {
    return this.health_;
  }
  toString() {
      return "x: " + this.x + " y: " + this.y + " health: " + this.getHealth();
  }
}

class Player extends Character {
  constructor(x, y, name) {
    super(x, y);
    this.name = name;
  }
  move(dx, dy) {
    this.x += dx;
    this.y += dy;
  }
  toString() {
    return "name: " + this.name + " " + super.toString();
  }
}

var x = process.argv[2];
var y = process.argv[3];
var name = process.argv[4];
var character = new Character(+x, +y);
character.damage();
console.log(character.toString());
var player = new Player(+x, +y, name);
player.damage();
player.move(7, 8);
console.log(player.toString());


```


# MODULE WITH NAME
- 今までJSでは言語レベルでモジュールの分割ができなかった
- ブラウザでJSをモジュール化してフロントで読み込む際は`require.js`や`browserify`といったライブラリで解決してきた
- ES6ではモジュール化するための専用構文が使えるようになった（`export`と`import`）

## 問題
- 以下のcommonjsで書かれたモジュールをES6のモジュールで書き換える
- 実行時は``<実行ファイル> <moduleの参照元ファイル>``の順序で渡す
  - 例：`tower-of-babel run program.js math.js`

```js
exports.PI = 3.141592;

    var _sqrt = function(s, x, last){
      return x != last ? _sqrt(s, (x + s / x) / 2.0, x) : x;
    };
    exports.sqrt = function(s){
      return _sqrt(s, s/2.0, 0.0);
    };
    exports.square = function(x) {
      return x * x;
    };

    var arg1 = process.argv[2];
    var arg2 = process.argv[3];
    var PI = require('./Math').PI;
    var sqrt = require('./Math').sqrt;
    var square = require('./Math').square;

    console.log(PI);
    console.log(sqrt(+arg1));
    console.log(square(+arg2));

```

```program.js

var arg1 = process.argv[2];
var arg2 = process.argv[3];

import {PI} from './math';
import {sqrt} from './math';
import {square} from './math';

console.log(PI);
console.log(sqrt(+arg1));
console.log(square(+arg2));

```

```math.js

export const PI = 3.141592;

var _sqrt = function(s, x, last){
  return x != last ? _sqrt(s, (x + s / x) / 2.0, x) : x;
};

export function sqrt(s) {
  return _sqrt(s, s/2.0, 0.0);
};

export function square(x) {
  return x * x;
};

```

# MODULES DEFAULT EXPORT
- `export`には通常の`export`と`default export`の2種類ある
  - `default export`の場合はexportする際に`export default`構文を付けて公開する。以下、例

```Message.js

const greeting = 'Hello';
const name = 'Babel';
const version = 'v5.0';
export default {
  greeting: greeting,
  name: name,
  version: version
};

```

```Main.js

import Message from './Message';
console.log(
  Message.greeting + ' ' + Message.name + ' ' + Message.version);

```

- 上記のようにimportする際に`{...}`で囲む必要がなく、exportされている対象の名前を知らなくて良い

## 問題
- 前回記述したexportで書かれた`math.js`と実行用のJSファイルを`default export`と`import`を使って書き換える

```program.js

var arg1 = process.argv[2];
var arg2 = process.argv[3];

import math from './math';

console.log(math.PI);
console.log(math.sqrt(+arg1));
console.log(math.square(+arg2));

```

```math.js

const PI = 3.141592;

var _sqrt = function(s, x, last){
  return x != last ? _sqrt(s, (x + s / x) / 2.0, x) : x;
};

function sqrt(s) {
  return _sqrt(s, s/2.0, 0.0);
};

function square(x) {
  return x * x;
};

export default {
  PI: PI,
  sqrt: sqrt,
  square: square
};

```

# BLOCK SCOPE
- ES6では`let`、`const`といった新しい変数宣言ができるように
  - `let`は最代入可能な変数、`const`は再代入不可能な変数
    - `const`はJavaでいう`final`的な
- これらの変数を使うことで、変数がスコープの外に漏れてしまうことを防ぐことができる

```block.js

{
  var a = 10;
  let b = 20;
  const tmp = a;
  a = b;
  b = tmp;
  // tmp = 30; 再代入はできない SyntaxErrorになる。
}

// a = 20、aはvarで宣言しているのでブロックスコープの外からも参照可能。
console.log(a);
// letで定義したbはブロックスコープの外からは解決できない、ReferenceError b is not defined になる。
console.log(b);
// constもスコープの中でのみ有効、tmp is not defined
console.log(tmp);

```

## 問題

```
'use strict';
// 変数 a はblockスコープの中でも外でも再代入可能な有効な変数です。
//変数宣言の方法(var|let|const)のいずれを利用するべきか検討してください
var|let|const a = 5;

// 変数 b は再代入不可能な変数です。
//変数宣言の方法(var|let|const)のいずれを利用するべきか検討してください
var|let|const b = process.argv[2];

if (a === 5) {
  // ここでの変数 c は再代入可能ですが、このblockの中でだけ有効な変数です。
  //変数宣言の方法(var|let|const)のいずれを利用するべきか検討してください。
  var|let|const c = 4;
  console.log(c);  // 4

  // ここでの変数 b はblockの中だけで有効な変数です。
  //変数宣言の方法(var|let|const)のいずれを利用するべきか検討してください。
  var|let|const b = 8;
  console.log(b); // 8
}

console.log(a); // 5
console.log(b);
try {
  // ここで cに対して値を変更してみましょう。
  c = 1000;
} catch (e) {
  // c is not defined エラーが発生することを確認して下さい。
  console.log(e);
}
```
```program.js
'use strict';
var a = 5;

const b = process.argv[2];

if (a === 5) {
  let c = 4;
  console.log(c);  // 4

  let b = 8;
  console.log(b); // 8
}

console.log(a); // 5
console.log(b);
try {
  // ここで cに対して値を変更してみましょう。
  c = 1000;
} catch (e) {
  // c is not defined エラーが発生することを確認して下さい。
  console.log(e);
}
```