PySpa統合思念体です。これからJavaScriptを覚えるなら、「この書き方はもう覚えなくていい」(よりよい代替がある)というものを集めてみました。
ES6以降の難しさは、旧来の書き方にプラスが増えただけではなく、大量の「旧来の書き方は間違いを誘発しやすいから非推奨」というものを作り出した点にあります。5年前、10年前の本やウェブがあまり役に立たちません。なお、書き方が複数あるものは、好き嫌いは当然あると思いますが、あえて過激に1つに絞っているところもあります。なお、これはこれから新規に学ぶ人が、過去のドキュメントやコードを見た時に古い情報を選別するためのまとめです。残念ながら、今時の書き方のみで構成された書籍などが存在しないからです。
たぶん明示的に書いていても読み飛ばす人はいると思いますが、すでに書いている人向けではありません。これから書くコードをこのスタイルにしていくのは別にいいと思いますが、既存のコードをすべて書き直せとか(後方互換性があるのでその必要性はありません)そういうものではありません。コードベースの安定が第一です。ちなみに、古い例と書いてある例の中に、僕が昨日まで書いていたコードもありますが、PySpaの総意で古い判定がされたものもあります。
TL;DR
もはや別言語です。
環境構築編
Babel
使わないとかTypeScriptとかも選択肢は一応ありますが、たとえpercelを使っていても避けられないのがBabelです。
Babelは昔は単独のツールだったものが、プラグインで拡張できるようになり、preset-es2015などのいくつかの設定を統括するプラグインが登場し、最終的にpreset-envというものに集約されました。なので、preset-es2015など、別の書き方をしている本や資料は古いです。
{
"presets": ["es2015"]
}
とりあえず最新の文法を解釈するようにするには、次のような設定ファイルを書きます。
{
"presets": ["@babel/preset-env"]
}
ブラウザのバージョンなど、「どの環境で動かしたいか?」という条件で出力フォーマットを設定できます。
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "6.10"
}
}]
]
}
もちろん、JSそのものをいじりたい(新しい文法を考案して提案するために実験したい)という人はこの限りではありません(が初心者向けユースケースではないので省略)。
変数・リテラル
const
をまず使う
昔は変数宣言で使えるのはvar
のみでした。
var name = "@wozozo";
今後、まっさきに使うべきはconst
です。var
は全部とりあえずconst
に置き換えて、上書きがどうしても必要なところだけlet
にします。
const name = "@wozozo";
関数型言語と同じで、変わる必要がないものは「もう変わらない」と宣言することで、脳内でコードをエミュレーションするときのメモリ消費を下げることができます。
変数を変更する場合は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
let
、const
はこのブロックの影響を受けます。
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
を付ける書き方も過去のものです。Promise
はasync
関数が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
を返したメソッドでは、Promise
のthen()
メソッドを呼ぶことで、約束を待ちましたが、可能な限り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
を使う
次のコードは古の時代からのコードです。
var iterable = [10, 20, 30];
for (var i = 0; i < iterable.length; i++) {
var value = iterable[i];
console.log(value);
}
次のコードは比較的新しいのですが今となってはより新しいコードもあります。一応、現在のiterable(Array
, Set
, Map
)のすべてで使えます。ただパフォーマンス上は遅いとされています(関数呼び出しが挟まるので)。
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したい場合などです。
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()
メソッドも、配列の実体を作っているわけではなくて、イテレータという小さいオブジェクトだけを返すので、要素数がどんなに大きくなろうとも、動作も軽いはずです。