「速習ECMAScript6」の感想・忘備録

kindle本「速習ECMAScript6」のまとめ。

点数

78

感想

ES2015だけに絞れこまれた内容だったが、しっかりと学ぶことができた。
2015年発売の本なので仕方がないが、現在(2020年3月)はbabelのバージョンが上がっているため、インストール方法や設定方法が変わってしまっていた。
その辺りを調べるのにかなり時間がかかってしまった。

主な内容

トランスコンパイラとPolyfillライブラリの違い

トランスコンパイラはコンパイルしてjsファイルを生成する、Polyfillは実行時に変換する。

babelを使って手動でトランスパイル

  1. node.jsをインストール
  2. babelをインストール
    書籍上に書かれているものはnpmパッケージ名が古かった。
    babel7からは@babel/core @babel/cli @babel/preset-env
    npm init -y
    npm install @babel/core @babel/cli @babel/preset-env
  3. babel.config.jsを作成
    babel6までは.babelrcだったが、babel7からはbabel.config.jsでも設定が可能になった。
    (babel.config.jsではJavaScriptでの記述が可能)
    module.exports = {
    presets: ['@babel/preset-env']
    };
  4. 変換
    babel test_es6.js -o test.js
    ※変更を監視する場合は-wオプションを付ける。
    ※ディレクトリ単位でのコンパイルは-dオプションで出力先ディレクトリを指定する
    babel .\source\ -d .\dist\ -w

Polyfillライブラリを有効化する

npmライブラリの@babel/preset-envがやってくれる。
(書籍では@babel/polyfillをインストールしているが、Babel 7.4.0で非推奨になった)

Babelがデフォルトで変換するのはJavaScriptの文法だけで、PromiseやSymbolなどの新しい組み込みオブジェクトやArrayクラスの新メソッドなどを利用するにはPolyfillライブラリ(polyfill.js)が必要になる。
<script src="/node_modules/babel-polyfill/dist/polyfill.js"></script>
としたり、entrypointのjsファイルに以下を追記するとすべてのpolyfillが埋め込まれる。

import 'core-js/stable';
import 'regenerator-runtime/runtime'; 


ただし、上記の方法はファイルサイズが大きくなってしまうので、あまり使われない。
以下の方法で、useBuiltInsを指定するのが一般的。
npm install core-js regenerator-runtime
でインストールし、babel.config.jsに
useBuiltIns: "usage"corejs: 3
を記述すると、どのpolyfillが必要かどうかを考える必要がなくなり、不要なpolyfillは含まれなくなる。
(regenerator-runtimeはasyncとawaitを使うために必要なpolyfill)

module.exports = {
  presets: [
    [
      '@babel/preset-env', {
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
};

※babelでPolyfillを使って変換されたソースはnode.jsのrequire文が使われているので、そのままブラウザで実行することはできないため(node.jsで実行はできる)、webpackやbrowserifyでバンドルする必要がある。

参考サイト

webpackでバンドルする手順

インストール

npm install --save-dev webpack webpack-cli babel-loader

webpack.config.jsを作成

const path = require('path');

module.exports = {
  mode: "development",
  devtool: 'inline-source-map',
  entry: path.resolve(__dirname, "src/test.js"),
  output: {
    path: path.resolve(__dirname, 'output'),
    filename: 'test.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: [['@babel/preset-env', {
                useBuiltIns: 'usage',
                corejs: 3
              }]],
            }
          }
        ]
      }
    ]
  }
};

※babel-loaderのoptionsはbabel.config.jsに記述してあるなら不要。どちらかに記述してあればよい。

babel.config.jsにuseBuiltIns: ‘usage’を指定してwebpackを実行すると、chromeで実行時に「test.js:7102 Uncaught TypeError: __webpack_require__(…) is not a function」となってしまった。超ハマった。
⇒exclude: /node_modules/を追加したらうまくいった。Promissのpolyfillはbabelで変換してしまうとブラウザでエラーになるらしい。
※babelの設定はプロジェクトのルートにあるbabel.config.jsが使用される

webpackコマンドを実行

バンドルされたjsファイルが生成される。

参考サイト

スコープの汚染を防ぐための即時関数は古い

従来のJavaScriptではグローバルスコープの汚染を防ぐ目的で即時関数が使われることがあったが、ES6ではやるべきではない。
{}で囲んでconst,letを使えばいいだけ。

タグ付きテンプレートリテラル

関数名の後ろにテンプレートリテラルを書いて関数を実行することができる。
※あまり使う機会はなさそう
myFunc `aaa${100}bbb${200}ccc`だと第一引数にテンプレート文字列配列、第2引数に可変長引数で埋め込まれた変数配列、が渡される。 myFunc(`aaa${100}bbb${200}ccc`)だと普通に文字列が渡される。

Symbol型

ユニークで不変なデータを定義することができる。
それ自身とのみ等しく、同じSymbolは2度と作ることはできない。
オブジェクトではないのでnew演算子を使うとエラーになる。
※あまり使う機会はなさそう

let hoge = Symbol();
let hoge2 = Symbol();
// hoge === hoge2はfalse

例えば定数定義で
const LOG_LEVEL_INFO = 1
としている場合、
if ($logLevel === LOG_LEVEL_INFO)
という条件は
if ($logLevel === 1)
と書くこともできてしまう。シンボルを使うと
if ($logLevel === LOG_LEVEL_INFO)
と書くしかない、というメリットがある。

分割代入

配列・オブジェクトから個々の要素を抽出する。
…演算子を利用すると、残りの要素がまとめて配列として代入される。

let [hoge, foo] = [15, 20];
let [hoge, foo, ...others] = [15, 20, 1, 39, 55];

// 以下のように、配列を返す関数から個々の変数へ値を代入することができる。
let [hoge, foo] = (function(){return [200, 300];})();

// 以下のようにすると、2つの変数の中身が入れ替わる。
[hoge, foo] = [foo, hoge];

オブジェクトからの分割代入は、変数名をキー名と同じにしなければならない(違う名前にすることもできるが、わかりづらくなる)
順番は関係ない。
let {area, name} = {name: 'マイケル', area: '東京'};
オブジェクトからの分割代入は変数宣言と同時に行った方がよい。
宣言済みの変数へオブジェクトから分割代入する場合は、{}がブロックとみなされないように()で囲む必要がある。
let area, name; ({area, name} = {name: 'マイケル', area: '東京'});
関数の仮引数にオブジェクトの分割代入を使うと、関数内で個々の変数としてアクセスすることができる。

function hoge({name, area}) {
  console.log(`${name}: ${area}`);
}
hoge({name: 'マイケル', area: '埼玉'});

展開(スプレッド)演算子

「…配列」「…オブジェクト」とすると中身が展開される。

const arr1 = [1, 2];
const arr2 = [...arr1]; // [1, 2]
const arr3 = [...arr1, 3, 4]; // [1, 2, 3, 4]

// 配列のマージが以下のように簡単になる
conat arr4 = [...arr1, ...arr2];

for of

配列や列挙可能オブジェクト(NodeListなど)の要素を取り出すことができる。
配列のループにはfor ofを使うべき。配列のインデックスが必要な場合はforEach、それ以外はfor ofとする。

Mapオブジェクト

Objectとの違い「文字列以外をキーにできる」「サイズを.sizeで取得できる」「for ofを使える」というメリットがある。

const map = new Map();
map.set('name', '山田');
map.set('prefName', '埼玉');
map.set('age', '55');
console.log(map.get('name')); // 山田
console.log(map.size); // 3
console.log(map.has('name')); // true
console.log(map.keys()); // name, prefName, age(Iteratorオブジェクト)
console.log(map.keys().next().value); // name(先頭のキー=配列ではないので[0]では取得できない)
console.log(map.values()); // 山田, 埼玉, 55(Iteratorオブジェクト)
for (let [key, value] of map) {
  console.log(`${key}:${value}`); // key, valueに分割代入される
}
map.delete('prefName');
map.clear(); // すべて削除

Setオブジェクト

値が重複しない配列みたいなもの。

const set = new Set();
set.add(100);
set.add(100);
set.add(50);
console.log(set.size); // 2
for (let value of set) {
  console.log(value);
}

Arrayオブジェクトに追加された主なメソッド

from(), keys(), values(), entries()

プロパティ省略記法

オブジェクトのプロパティ名と同じ名前の変数は、値の指定を省略できるようになった。

const title = 'hoge';
const obj = { title }; // { "title": "hoge" }

functionキーワードの省略

メソッド定義の「function」を省略できるようになった。

const obj = {
  myFunc() {
    console.log('myFunc!');
  }
}

class

class定義ができるようになった。
ただし、publicなどのアクセス修飾子はない。static修飾子はある。
コンストラクタはconstructor(){}で定義。

主な注意点

  • サブクラスのコンストラクタではsuper();をコールしないとエラーになる
    (javaのような親コンストラクタの暗黙的な呼び出しは行われない)
  • クラス内ではメソッドしか定義できない。
  • プロパティはコンストラクタで定義するか、ゲッター/セッターで定義する。
// ゲッター
get x() {
  return this._x;
}

ジェネレーター

イテレータを返す関数を定義することができる。
functionの後に*をつけるとジェネレータ関数となる。戻り値はfor ofでループさせたりnextで次の値を取得したりできる。
※使う機会はなさそう

function* myGenerator() {
  yield 'A';//1回目のnext関数で返す
  yield 'B';//2回目のnext関数で返す
  yield 'C';//3回目のnext関数で返す
}

モジュール

import, exportが使えるようになった。

  • import文で指定するファイルは拡張子を省略してもよい
  • ブラウザで実行する場合はscriptタグにtype="module"が必要
  • node.jsで実行する場合は「import,exportを使っているファイルの拡張子を.mjsにする」「実行時に–experimental-modulesオプションを付ける」をしないとエラーとなる。
  • デフォルトエクスポート
    export default 'hoge';
    でエクスポート。
    import name from './export';
    でインポート。
  • 名前付きエクスポート
    export const name = 'hoge';
    export const age = 39;
    でエクスポート。
    import { name, age } from './export';
    でインポート。
  • デフォルトエクスポートが推奨されている
    なぜなら、簡潔な方がいいから。
export default { name: 'okada' };
import person from './export';
console.log(person.name);

実行コマンド「node --experimental-modules import.mjs

babelは内部的にimport命令をnode.jsのrequire関数に変換する。
require関数をブラウザで動作させるにはwebpackやbrowserifyでさらに変換する必要がある。
以下、browserifyの実行方法(Babelify=BabelをBrowserifyで使えるようにする為のライブラリ)
npm install -g browserify
npm install --save-dev babelify
browserify import.js export.js -t [ babelify --presets es2015 ] -o bundle.js


【新しい記事】

【古い記事】

コメントを残す

メールアドレスが公開されることはありません。