PySpa統合思念体です。これからJavaScriptを覚えるなら、「この書き方はもう覚えなくていい」(よりよい代替がある)というものを集めてみました。

ES6以降の難しさは、旧来の書き方にプラスが増えただけではなく、大量の「旧来の書き方は間違いを誘発しやすいから非推奨」というものを作り出した点にあります。5年前、10年前の本やウェブがあまり役に立たちません。なお、書き方が複数あるものは、好き嫌いは当然あると思いますが、あえて過激に1つに絞っているところもあります。なお、これはこれから新規に学ぶ人が、過去のドキュメントやコードを見た時に古い情報を選別するためのまとめです。残念ながら、今時の書き方のみで構成された書籍などが存在しないからです。

たぶん明示的に書いていても読み飛ばす人はいると思いますが、すでに書いている人向けではありません。これから書くコードをこのスタイルにしていくのは別にいいと思いますが、既存のコードをすべて書き直せとか(後方互換性があるのでその必要性はありません)そういうものではありません。コードベースの安定が第一です。ちなみに、古い例と書いてある例の中に、僕が昨日まで書いていたコードもありますが、PySpaの総意で古い判定がされたものもあります。

TL;DR

もはや別言語です。

環境構築編

Babel

使わないとかTypeScriptとかも選択肢は一応ありますが、たとえpercelを使っていても避けられないのがBabelです。

Babelは昔は単独のツールだったものが、プラグインで拡張できるようになり、preset-es2015などのいくつかの設定を統括するプラグインが登場し、最終的にpreset-envというものに集約されました。なので、preset-es2015など、別の書き方をしている本や資料は古いです。

古い.babelrc
{
  "presets": ["es2015"]
}

とりあえず最新の文法を解釈するようにするには、次のような設定ファイルを書きます。

今時の.babelrc
{
  "presets": ["@babel/preset-env"]
}

ブラウザのバージョンなど、「どの環境で動かしたいか?」という条件で出力フォーマットを設定できます。

今時の.babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "node": "6.10"
      }
    }]
  ]
}

もちろん、JSそのものをいじりたい(新しい文法を考案して提案するために実験したい)という人はこの限りではありません(が初心者向けユースケースではないので省略)。

変数・リテラル

constをまず使う

昔は変数宣言で使えるのはvarのみでした。

古い書き方
var name = "@wozozo";

今後、まっさきに使うべきはconstです。varは全部とりあえずconstに置き換えて、上書きがどうしても必要なところだけletにします。

何はともあれconst
const name = "@wozozo";

関数型言語と同じで、変わる必要がないものは「もう変わらない」と宣言することで、脳内でコードをエミュレーションするときのメモリ消費を下げることができます。

変数を変更する場合はletを使います。

変更がある変数はlet
// 変更前
let name;
if (mode === "slack") {
    name = "youichi";
} else if (mode === "twitter") {
    name = "@wozozo";
}

なお、C++のconstを知っている人からすると違和感があるかもしれませんが、constで宣言した変数に格納された配列は、代入し直すのはできませんが、配列に要素を追加することはできます。オブジェクトの属性変更もできます。そのため、使える場所はかなり広いです。

なお、varも、グローバルスコープに変数を置く場合にはまだ使えますが、後述するように本当の意味でのグローバルスコープを扱う機会は減っているので基本的に「使わない」で良いでしょう。

変数のスコープ

以前は{}はブロックにはなりましたが、変数の範囲とはリンクしていませんでした。

古いコード
for (var i = 0; i < 10; i++) {
    // do something
}
console.log(i); // -> 10

letconstはこのブロックの影響を受けます。

新しいコード
for (let i = 0; i < 10; i++) {
    // do something
}
console.log(i); // ReferenceError: i is not defined

ifとかforとか制御構文を使わずに、おもむろに{}のブロックを書いてスコープを制限することもできます。スコープが狭くなると、変数の影響範囲が小さくなるのでコードの理解がしやすくなります。若者であれば記憶力は強いので良いですが、歳をとるとだんだん弱ってくるのです。また、若くても二日酔いの時もあるでしょうし、風邪ひいたり疲れている時もあると思うので、頑張らないで理解できるように常にしておくのは意味があります。

文字列の結合

従来は他の言語でいうprintf系のようなものがなく、文字列を+で結合したり、配列に入れて.join()で結合したりしましたが、これは古いコードです。

古いコード
console.log("[Debug]:" + variable);

いまどきは文字列テンプレートリテラルというのがありますので、これを使います。printfのような数値の変換などのフォーマットはなく、あくまでも文字列結合をスマートにやるためのものです。もちろん、数が決まらない配列などは従来どおり.join()を使います。

新しいコード
console.log(`[Debug]: ${variable}`);

オブジェクトのコピー

Reduxでimmutable.jsを使わない、という状況ではよくオブジェクトのコピーが発生します。

古いコード
var destObj = {};
for (var key in srcObj) {
  if (srcObj.hasOwnProperty(k)) {
    destObj[k] = srcObj[k];
  }
}

今時はObject.assign()というクラスメソッドを使います。

新しいコード
const destObj = {};
Object.assign(destObj, srcObj);

さらに将来は次のようになります。このピリオド3つは後でも出てきます。

新しいコード
const destObj = {...srcObj};

クラス宣言

昔は関数とprototypeという属性をいじくり回してクラスを表現していました。正確には処理系的にはクラスではないのですが、コードのユーザー視点では他の言語のクラスと同等なのでクラスとしてしまいます。

古いクラスの表現
// 関数だけどコンストラクタ
function SmallAnimal() {
    this.animaltype = "ポメラニアン";
}

// こうやって継承
SmallAnimal.prototype = new Parent();

// こうやってメソッド
SmallAnimal.prototype.say = function() {
    console.log(this.animalType + "だけどMSの中に永らく居たBOM信者の全身の毛をむしりたい");
};

var smallAnimal = new SmallAnimal();
smallAnimal.say();
// ポメラニアンだけどMSの中に永らく居たBOM信者の全身の毛をむしりたい

なお、この仕組みをラップした自前のextends関数みたいなのを使ってクラスっぽいことを表現しようという、なんとかThe Good Partsに影響された一派も一時期いましたが、百害あって一理なしです。

今時の書き方は次のようなclassを使った書き方です。「これはシンタックスシュガーで云々」みたいに言ってくるオッサンの顔面にはパンチをしてもいいです。

新しいクラスの表現
// 関数だけどコンストラクタ
class SmallAnimal extends Parent {
    constructor() {
        this.animaltype = "ポメラニアン";
    }

    say() {
        console.log(`${this.animalType}だけどMSの中に永らく居たBOM信者の全身の毛をむしりたい`);
    }
}

関数宣言

アロー関数のみにしていく

functionキーワードはもう捨てましょう!functionキーワードのthisの取り扱いはトラブルの元です。もう存在しなかったものとして歴史の闇に葬ります。次の書き方は古いfunctionキーワードを使っています。こういう説明を見かけたらゴミ箱にダンクシュートします。

古いの関数定義
function name(引数) {
    本体
}

今時はアロー関数を使って書いていきます。特に、今時の書き方は、JavaScriptでよく扱う、無名関数との相性が非常に高くなっています。

今時の関数定義
const name = (引数) => {
    本体
};

状況によってはカッコやreturnなどが省略できたりしますが、そこは割愛します。

即時実行関数はもう使わない

関数を作ってその場で実行することで、スコープ外に変数などが見えないようにする、というテクニックがかつてありました。即時実行関数と呼びます。今時であれば、WebPackなりBrowserifyなりRollupなりPercelなりでファイルを結合するでしょうし、昔のように1ファイルでライブラリを配ってそれを<script>タグで読み込む、というのは減ってきていると思います。

そのため、こういう書き方自体も減ってきています(もちろん、なくなってはいません)。ただ、タグ一つで読み込めるのは利用者にとっては便利なので、超絶良いフレームワーク的なライブラリができて公開したい、という気持ちが出てきてからで遅くはありません。今は無視しましょう。

古いテクニックである即時実行関数
var lib = (function() {
  var libBody = {};

  var localVariable;

  libBody.method = function() {
      console.log(localVariable);
  }
  return libBody;
})();

function(){}をかっこでくくって、その末尾に関数呼び出しのための()がさらに付いている感じです。これで、エクスポートしたい特定の変数だけをreturnで返して公開をしていました。

今時はES6スタイルであればexport { name1, name2, …, nameN };といった書き方が使えます。Browserify/Node.jsでもmodule.exports = { name1: name1, name2: name2... }となります。公開しているもの以外は非公開です。なので、堂々とグローバル空間に置いても問題ありません。

非同期処理

JavaScriptで中級以降になってくると避けられないのが非同期処理です。以前はコールバック地獄と揶揄されるような感じでした。(なお、エラー処理時にかっこなしのif文を一行で書く、というスタイルは好き嫌いの別れるところだと思うので、ここではあえて触れません)。

コールバック地獄
func1(引数, function(err, value) {
  if (err) return err;
  func2(引数, function(err, value) {
    if (err) return err;
    func3(引数, function(err, value) {
      if (err) return err;
      func4(引数, function(err, value) {
        if (err) return err;
        func5(引数, function(err, value) {
          // 最後に実行されるコードブロック
        });
      });
    });
  });
});

その後、非同期処理の待ちはPromiseを使うようになりました。これで、ネストはだいぶ浅くなるので、書きやすくなりました。

ちょっと今時の書き方
const getData = (url) => {
    fetch(url).then(resp => {
        resp.json();
    }).then(json => {
        console.log(json);
    });
}; 

ただ、new Promise(...)と、Promiseの前にnewを付ける書き方も過去のものです。Promiseasync関数がreturnとともに作るものです。

今時はasync/awaitキーワードをアロー関数の前に付与します。functionの前にも付けられますが、functionはもはやNGワードなので忘れてください。

asyncとつくと、その関数がPromiseというクラスのオブジェクトを返すようになります。Promiseはその名の通り「重たい仕事が終わったら、あとで呼びに来るからね」という約束です。awaitは、その約束が守られるのを粛々と待ちます。

今時の非同期処理
// 非同期処理をawaitで待つ
const fetchData = async (url) => {
    const resp = await fetch(url);
    const json = await resp.json();
    console.log(json);
};

Promiseを返したメソッドでは、Promisethen()メソッドを呼ぶことで、約束を待ちましたが、可能な限りawaitを使って、then()も滅ぼしましょう。

Promise.all()などのところでPromiseは使うので、この名前を禁止することはありません。

apply()

昔は、関数に引数セットを配列で引き渡したいときはapply()というメソッドを使っていました。

古い書き方
function f(a, b, c) {
    console.log(a, b, c);
}

// a=1, b=2, c=3として実行される
f.apply(null, [1, 2, 3]);

この関数の中のthisを最初の引数で指定したりとかありましたが、関数宣言をすべてアロー関数にするのであれば、もうそういうのも過去の話です。配列展開の文法のスプレッド演算子...を使うと同じようなことができます。

新しい書き方
const f = (a, b, c) => {
    console.log(a, b, c);
};

f(...[1, 2, 3]);

なお、apply()がよく出てきていた文脈としては、関数を可変長配列にしたい、というものでした。関数の中ではargumentsという名前のオブジェクトが関数の引数を持っているのですが、これが配列のようで配列でない、ちょっと配列っぽいオブジェクトです。ちょっと使いにくいので、一旦本物の配列にする時にapply()を使ったハックがよく利用されていました。何が起きているかは理解する必要はありません。

可変長配列の古いコード
function f() {
    var list = Array.prototype.slice.call(arguments);
    console.log(list);
}
f(1, 2, 3, 4, 5, 6);
// [1, 2, 3, 4, 5, 6];

これもスプレッド演算子を使うことでわかりやすくなります。もともとの方法は関数宣言だけを見ても実際の引数の数がわかりにくいという問題がありましたが、こちらの方がわかりやすいでしょう。

可変長配列の新しいコード
const f = (a, b, ...c) => {
    console.log(a, b, c);
};
f(1, 2, 3, 4, 5, 6);
// 1, 2, [3, 4, 5, 6];

配列、辞書

ES6では、単なる配列以外にも、Map/Setなどが増えました。これらは子供のデータをフラットにたくさん入れられるデータ構造で、ループの中で一個ずつ子供を取得する(イテレーション)できるので、iterableと呼ばれます。そのため、配列固有の操作じゃなくて、iterable共通の操作にしていくことが、2018年のESの書き方になります。

ループはfor ... ofを使う

次のコードは古の時代からのコードです。

古いループ1
var iterable = [10, 20, 30];

for (var i = 0; i < iterable.length; i++) {
  var value = iterable[i];
  console.log(value);
}

次のコードは比較的新しいのですが今となってはより新しいコードもあります。一応、現在のiterable(Array, Set, Map)のすべてで使えます。ただパフォーマンス上は遅いとされています(関数呼び出しが挟まるので)。

古いループ2
var iterable = [10, 20, 30];

iterable.forEach(value => {
  console.log(value);
});

イテレータプロトコルという言語組み込みがこれです。今後も新しいiterableが出たとしてもずっと使い続けられます。ループを回して、要素をひとつずつ取り出す・・・というコードはfor ... ofを使います。MDNのサンプルをちょっと改変。

新しいループ
const iterable = [10, 20, 30];

for (let value of iterable) {
  console.log(value);
}

こちらは関数呼び出しを伴わないフラットなコードなので、async/awaitとも一緒に使えます。配列の要素を引数にして、1つずつasyncしたい場合などです。

asyncと新しいループ
const iterable = [10, 20, 30];

for (let value of iterable) {
  async doSomething(value);
}

辞書・ハッシュ用途はオブジェクトではなくてMapを使う

古のコードはオブジェクトを、他言語の辞書やハッシュのようにつかっていました。

古いコード
var map = {
  "五反田": "約束の地",
  "戸越銀座": "TGSGNZ"
};

for (var key in map) {
    if (map.hasOwnProperty(key)) {
        console.log(key + " : " + map[key]);
    }
}

今時はMapを使います。他の言語のようにリテラルで簡単に初期化できないのはあれですが、最初の部分だけですので我慢してください。

新しいコード
const map = new Map([
  ["五反田", "約束の地"],
  ["戸越銀座", "TGSGNZ"]
]);

for (const [key, value] of map) {
    console.log(`${key} : ${value}`);
}

keyだけでループしたい場合(以前同様)はfor (const key of map.keys()), valueだけでループしたい場合はfor (const key of map.values())が使えます。

keys()メソッド、values()メソッドも、配列の実体を作っているわけではなくて、イテレータという小さいオブジェクトだけを返すので、要素数がどんなに大きくなろうとも、動作も軽いはずです。