JavaScriptの不思議な記法をBabelを通して解釈してみる

- この記事は[MDCアドベントカレンダー](https://qiita.com/advent-calendar/2020/mdc)3日目の記事です
- ちなみに1日目は[[PWA/A2HS]Webアプリをインストールしてホーム画面へ追加できるようにする](https://qiita.com/ozaki25/items/7994e2cf03b61592bb1d)を書きました

## 概要

- JavaScriptは言語仕様であるEcmaScriptのバージョンによって仕様が定義されています
    - 参考：[ECMAScriptの使い方](https://azu.github.io/slide/2018/node/ecmascript39.html)
- EcmaScriptのバージョン6(ES6/ES2015)で大きなアップデートが入って以降は様々な記法が追加され、ES5の時代しか知らない人からすると奇妙な書き方も増えてきました
    - ES6からは毎年VUPされることとなりES2015のように年をバージョンとして扱うようになった
- 今回はJavaScriptのコードを異なるEcmaScriptのバージョンにトランスパイルできるBabelを使って奇妙な記法をES5に変換し、従来の書き方でいうとどう書くか見ることで動作を理解したいと思います

## アロー関数

- アロー関数は比較的わかりやすい構文ですが、初見では `=>` があったり一行だと省略できるものが多くて戸惑うこともあるかもしれません

### Babel実行前後の比較

- 大雑把にいうと `=>` と `function` が置き換わっただけですね（厳密にはそれだけではないが）
- [Babelの実行結果](https://babeljs.io/repl#?browsers=defaults&build=&builtIns=false&spec=false&loose=false&code_lz=MYewdgzgLgBAhgEwTAvDAFHANDARgSlQD54YBqPAbgChRJYAnAUwgFcAbWNRBdARhwAmfDToQQ7JgDp2IAObpmbToQD0qmAGZqQA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ces2015%2Ces2016%2Ces2017%2Ces2015-loose&prettier=true&targets=&version=7.12.9&externalPlugins=)

```js:ES2015
const add = (a, b) => a + b;
const result = add(1, 2);
console.log(result) // 3
```

```js:ES5
var add = function add(a, b) {
  return a + b;
};

var result = add(1, 2);
console.log(result); // 3
```

## プロパティ名の省略

- 省略記法なので初見だと何をしているのか一瞬迷いますね
- 一度覚えてしまえば簡単だしコードがすっきり見えていいですね

### Babel実行前後の比較

- オブジェクトのkey名とvalueに設定する変数名が同様の場合省略できるというものです
- [Babelの実行結果](https://babeljs.io/repl#?browsers=defaults&build=&builtIns=false&spec=false&loose=false&code_lz=MYewdgzgLgBGCGBbApjAvDA5CAXvA1gJYBMArJgNwBQoks8A5qhgMwAM1N40MArhMgBO6GAG8qMOEmQAaCTEayqAX060IIADbIAdJpAMAFPyEBKCjAD0lsVJQAuLLgIlyMyYsfsYyqkA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ces2015%2Ces2016%2Ces2017%2Ces2015-loose&prettier=true&targets=&version=7.12.9&externalPlugins=)

```js:ES20XX
const name = 'ozaki25';
const age = 30;

const user = {
  name,
  age,
};

console.log(user); // { name: 'ozaki25',  age: 30 }
```

```js:ES5
var name = "ozaki25";
var age = 30;
var user = {
  name: name,
  age: age
};
console.log(user); // { name: 'ozaki25',  age: 30 }
```

## 分割代入

- 分割代入はよく見ればなんとなく想像つきそうですが、左辺の書き方が見慣れないかもしれません。

### Babel実行前後の比較

- `const { name } = user;` が `var name = user.name;`の、`const [dog, cat] = animals;` が `var dog = animals[0], cat = animals[1];` のショートカットになってる感じですね

#### Object

- [Babelの実行結果](https://babeljs.io/repl#?browsers=defaults&build=&builtIns=false&spec=false&loose=false&code_lz=PTAEHkCMCsFMGMAuAoeB7AdgZ0aArlrAE6gC8oA3sqKBgIYC2sAXKAORoBedA1gJYAmAKxsANNVB0A5i1ABmAAziAvgG5kqTDkq1GsUMrL5CRdemxoANrAB0ltFIAU9JgEpVoEKC69BQoA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ces2015%2Ces2016%2Ces2017%2Ces2015-loose&prettier=true&targets=&version=7.12.9&externalPlugins=)

```js:ES20XX
// Object
const user = {
  name: 'ozaki25',
  age: 30,
};

const { name } = user;
console.log(name); // ozaki25
```

```js:ES5
// Object
var user = {
  name: "ozaki25",
  age: 30
};
var name = user.name;
console.log(name); // ozaki25
```

#### Array

- [Babelの実行結果](https://babeljs.io/repl#?browsers=defaults&build=&builtIns=false&spec=false&loose=false&code_lz=PTAEEECdIQwTwFAGMD2A7AzgF1DNBLAWxgBsNQBeUAbQHJAap1oBpRbBqZ1oF1l1saATFAHMWSGFk6VcBYmQDcPTChIBTAHQlhACkFCAlHNAhQ9RRmXrNQrWKwGjYNkA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ces2015%2Ces2016%2Ces2017%2Ces2015-loose&prettier=true&targets=&version=7.12.9&externalPlugins=)

```js:ES20XX
// Array
const animals = ['犬', '猫']
const [dog, cat] = animals;
console.log(dog); // 犬
console.log(cat); // 猫
```

```js:ES5
// Array
var animals = ["犬", "猫"];
var dog = animals[0],
  cat = animals[1];
console.log(dog); // 犬
console.log(cat); // 猫
```

## スプレッド構文

- 初めて見る人は `...` でもう意味不明ですね

### Babel実行前後の比較

- Objectの方は長くてドン引きですね。実行結果から想像つくと思いますがObjectをコピーして新しいObjectに埋め込んでいます。これを `...` で表現できちゃうので便利な構文ですね（Babelの結果を見て理解を深めるという趣旨が・・・）
    - 同じkey名の値が複数回登場したら後勝ちになります
- Arrayの方は短くてわかりやすいですね。`[...xx]`は[concat](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/concat)にそのまま置き換えられます。`...` で配列のコピーを展開して新しい配列に埋め込んでいます
    - concatでも記述量は大して変わりませんが視覚的に新しい配列をイメージできるのがいいですね
 
#### Object

- [Babelの実行結果](https://babeljs.io/repl#?browsers=defaults&build=&builtIns=false&spec=false&loose=false&code_lz=PTAEHkCMCsFMGMAuAoeB7AdgZ0aArlrAE6gC8oA3qBgIYC2sAXKAORoBeNA1gJYBMAVhYAaUDQDmTUAGYADKAC-AblSYc1WAHcAqoRLkq0NJGYsA6rEiAKhkDPDIA6GQNMMgIoYRoAHQeCxRSvTY0ADawbgFo4gAUGFq6xACUSqAglKBGJqwW1vbOrrQMphzc_EKiElJyishAA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ces2015%2Ces2016%2Ces2017%2Ces2015-loose&prettier=true&targets=&version=7.12.9&externalPlugins=)

```js:ES20XX
// Object
const user = { name: 'ozaki25', age: 30 };
const newUser = { job: 'Webエンジニア', ...user };
console.log(newUser); // { job: 'Webエンジニア', name: 'ozaki25', age: 30 }
```

```js:ES5
function ownKeys(object, enumerableOnly) {
  var keys = Object.keys(object);
  if (Object.getOwnPropertySymbols) {
    var symbols = Object.getOwnPropertySymbols(object);
    if (enumerableOnly)
      symbols = symbols.filter(function (sym) {
        return Object.getOwnPropertyDescriptor(object, sym).enumerable;
      });
    keys.push.apply(keys, symbols);
  }
  return keys;
}

function _objectSpread(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i] != null ? arguments[i] : {};
    if (i % 2) {
      ownKeys(Object(source), true).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      });
    } else if (Object.getOwnPropertyDescriptors) {
      Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
      ownKeys(Object(source)).forEach(function (key) {
        Object.defineProperty(
          target,
          key,
          Object.getOwnPropertyDescriptor(source, key)
        );
      });
    }
  }
  return target;
}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

// Object
var user = {
  name: "ozaki25",
  age: 30
};

var newUser = _objectSpread(
  {
    job: "Webエンジニア"
  },
  user
);
```

#### Array

- [Babelの実行結果]()

```js:ES20XX
// Array
const animals = ['犬', '猫']
const newAnimals = ['パンダ', 'ライオン', ...animals]
console.log(newAnimals); // ['パンダ', 'ライオン', '犬', '猫']
```

```js:ES5
// Array
var animals = ["犬", "猫"];
var newAnimals = ["パンダ", "ライオン"].concat(animals);
console.log(newAnimals); // ['パンダ', 'ライオン', '犬', '猫']
```

## オプショナルチェイニング

- 他の言語で知ってる人は違和感ないと思いますが、知らない人だとこの `?` は何？となりがちです

### Babel実行前後の比較

- `user1.team?.name` の部分は `(_user1$team = user1.team) === null || _user1$team === void 0 ? void 0 : _user1$team.name;` で表現されています
    - もし `user.team.name` と書いてteamが `undefined` だった場合エラーで落ちてしまいます
    - ?をつけておくと `team` が `null` や `undefined` だった場合エラーにさせずその場で `undefined` を返却してくれます
- [Babelの実行結果](https://babeljs.io/repl#?browsers=defaults&build=&builtIns=false&spec=false&loose=false&code_lz=MYewdgzgLgBArhApgJwIwwLwwN4CgYxgCGAtogFwwDkIAXkQNYCWATAKxUA0-MUiplPAQLEylKgCU4YMCghceAX26LcuUJFgIULTDh6iK1Oo1YA2BarUbovfiQBypROiza0AOj6kA_B8Pq4BAgADaIHiEgAOYAFN6OzqgAlADcMAD06TBSMnLWQbDxTmS6bkjILF72fgE2oeGRsUXOLKkZWdIAJogAZkyynbhAA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ces2015%2Ces2016%2Ces2017%2Ces2015-loose&prettier=true&targets=&version=7.12.9&externalPlugins=)

```js:ES20XX
const user1 = {
  name: 'ozaki25',
  team: {
    name: 'Runners',
  },
}

const user2 = {
  name: 'ozaki26',
}


const teamName1 = user1.team?.name
console.log(teamName1); // Runners

const teamName2 = user2.team?.name
console.log(teamName2); // undefined
```

```js:ES5
var _user1$team, _user2$team;

var user1 = {
  name: "ozaki25",
  team: {
    name: "Runners"
  }
};
var user2 = {
  name: "ozaki26"
};
var teamName1 =
  (_user1$team = user1.team) === null || _user1$team === void 0
    ? void 0
    : _user1$team.name;
console.log(teamName1); // Runners

var teamName2 =
  (_user2$team = user2.team) === null || _user2$team === void 0
    ? void 0
    : _user2$team.name;
console.log(teamName2); // undefined
```

## まとめ

- とりあえず思いついたものだけ書きましたが他にも初見殺しの記法はいろいろありますね
- どれも慣れてしまえば便利な構文なので当たり前に使えるようになっていきましょう