JavaScriptで密かに誤解されていること5選

皆さんこんにちは。この記事では、JavaScriptに関して誤解している人が多そうな小ネタを5個ほど集めましたので紹介します。では早速どうぞ。


...は「スプレッド演算子」ではない

スプレッド演算子ではなくスプレッド構文です。

...はES2015で追加された便利な構文です。次のように配列をコピーしたり、関数呼び出しの引数に配列を展開したりできます。


スプレッド構文の例

const arr1 = [1, 2, 3];

const arr2 = [...arr1, 4, 5];

func(...arr1, ...arr2);


また、ES2018からはオブジェクトの中でも...が使えます。

当初この...を「スプレッド演算子」(spread operator)と呼ぶ向きがありましたが、よく見るとこれは全然演算子ではありませんね。

演算子の定義は人によって異なるかもしれませんが、「いくつかの式から式を作る働きをする構文」というのが一般に受け入れられている定義だと思います。例えばx + 1という式は、xという式と1という式を+で繋げる事でx + 1という式を得ています。この働きをする+が演算子です。

こうしてみると、...は式を作るのではありません。つまり、const arr2 = ...arr1;のようなものは受け付けられないということです。...が使えるのは配列リテラル・オブジェクトリテラルの中と関数引数の中だけであり、どちらかといえばこれらの構文に組み込まれたものであると見るべきです。

ですから、...は演算子とは呼びがたいのです。実際、ECMAScript仕様書を見ても...を「演算子」と呼んでいるところはありません。

実は明確に「スプレッド構文」と呼んでいるわけでもなく単にspreadと呼ばれているだけなのですが、MDNがspread syntaxという名前を採用していることなどからここではスプレッド構文という名前を用いて説明しています。

なお、さらっとECMAScript仕様書という概念が出てきましたが、これはJavaScriptというプログラミング言語の仕様を決めるもので、Ecma Internationalという国際機関によって管理されています。仕様書は現在では年に1回くらい更新され、JavaScriptの機能追加が行われます。ES何とかというのはJavaScriptのバージョン名というわけです。

ECMAScript仕様書はJavaScriptに関する絶対に正しい事実源ですので、皆さんも困ったらぜひ参照してみてください。

では次のネタに移ります。


async/awaitはES7ではない

ES8です。

async/awaitPromiseを用いた非同期処理をいい感じに扱うためのすてきな構文ですが、これが導入されたECMAScriptバージョンが「ES7」であるとの勘違いがよく見られます。「ES7 async/await」のような言い方をしているのをたまに見ますね。

当初の予定はそうだったのかもしれませんが、実際のところasync/awaitが導入されたのはES8です。


そもそもES7とかES8は正式名称ではない

これらは古い呼び方であり、ES2016ES2017という言い方が正しいです。

ES5が登場したのが2009年で、その次のバージョンはHarmonyとかES6とか呼びながら長い間議論されてきました。それゆえ、ES6という名前に聞き覚えがある人は多いでしょう。

しかし、最終的にはES5の次のバージョンはES2015が正式名称になりました。すなわち、ES6とES2015は同じものを指しており、ES6というのは古い呼び方ということになります。一の位がひとつずれていてややこしいので気をつけましょう。

この名残で、その次のバージョンでありES2016をES7と呼び、ES2017をES8と呼び……という呼び方がたまに見られます。現行の最新バージョンであるES2019はES10になるのでしょうか。

ちなみに、6, 7, 8……というナンバリングが完全に消滅したわけではありません。ES2019の仕様書のタイトルを見るとこのように書かれています。

スクリーンショット 2019-06-27 14.12.59.png

ECMA-262, 10th edition, June 2019

ECMAScript®︎ 2019

Language Specification

ということで、最初に10th editionと書いてありますね。これは、言語の名称ではなくなったが、仕様のバージョン番号としては生きているということになります。

ややこしいですが、勘違いしないでいただきたいのはじゃあES10のような言い方が正式に正しいかというと、そうではないということです。仕様書の最初の2文を引用します。


This Ecma Standard defines the ECMAScript 2019 Language. It is the tenth edition of the ECMAScript Language Specification.


ここに書いてあることが答えですね。言語の名前(あるいは言語のバージョン番号)がES2019で、仕様書のバージョンが10なのです。昔(ES5まで)は両者は一緒でしたが、今は違うというわけです。

次はちょっと話が変わります。


オブジェクトのキーの列挙順は全く保証されていないわけではない

ES2015以降のメソッドを使う場合は列挙順が保証されています。Object.definePropertiesも、ES5ですが例外的に列挙順が保証されています。)

また、for-inなどの古い方法の場合もできるだけ仕様化しようぜという議論があります現在Stage 2)。

実は、for-inとかObject.keysを使ってオブジェクトのキーを列挙する場合の順番は実装依存で、仕様で並び順が定義されているわけではありませんでした。なので「JavaScriptではオブジェクトのキーの列挙順は保証されていない」と思っている人がいるようです。

しかし100%保証されていないわけではなく、最近のAPI(Object.getOwnPropertyNamesなど)はちゃんと列挙順が保証されています。その一方、ES5以前のメソッドでは後方互換性のために列挙順が定義されないままになっています(保証されていなかったものを保証すると後方互換性が崩れるというのも変な話ですが)。

筆者が書いた記事ではありませんが詳しい記事がありましたのでこちらに説明を譲ります。


オブジェクトを真偽値に変換すると必ずtrueになるわけではない

document.allを真偽値に変換するとfalseになります。

JavaScriptでは原則としてオブジェクトを真偽値に変換するとtrueになりますが、このdocument.allだけは例外です。document.allはオブジェクトなのでdocument.all.fooのようにプロパティアクセスができたりするのですが、それにも関わらずdocument.allは真偽値に変換するとfalseなのです。また、document.allが持つ特異な挙動はそれだけではなく、document.all == undefinedtrueになったりtypeof document.all"undefined"になるなどの特徴もあります。

もっとも、document.allなどというものは古代の遺物でありWeb界の黒歴史なので、皆さんがこの事実を気にかける必要はまったくありません。なぜこんな挙動があるのかというのは皆さんのご想像(あと別の記事)にお任せしますが、JavaScript(というかWeb)は後方互換性を大事にする業界なので今でもこの黒歴史を背負って生きているのです。

なお、この「オブジェクトだけど真偽値に変換するとfalseになる」という挙動は[[IsHTMLDDA]]内部スロットとしてECMAScript仕様書に記載されていますが、そのような挙動を実際に体現するdocument.allというオブジェクトの存在はHTML仕様書で定義されています。

昔はECMAScript仕様書にそのような記載はなく、HTML仕様書が「このオブジェクトは真偽値に変換するとfalseになるんでよろしく^^」と勝手に書いているという状態でした。この状況に変化があったのが2016~2017年のことで、最終的にこの挙動の定義はECMAScriptに移管されました。そのときの議論はecma262リポジトリのissueで見ることができます。

発端は「言語の基礎的な仕様がHTML仕様書に勝手に変更されていいのか? 我々で管理すべきでは?」という意見です。この議論では「HTMLの仕様書に書いておけばそれでいい派」「ちゃんとECMAScript仕様書に載せるべき派」「ECMAの管轄にしたいけどECMAScript仕様書には入れたくない派」などが戦いました。なかなか面白いので興味がある方は読んでみていただきたいのですが、「JavaScriptはWebブラウザだけが実装するものではない。こんなWebの黒歴史のためにブラウザ以外の環境にまで影響を与える(=ECMAScript仕様書に取り入れる)のはどうなのか」とか「この挙動がECMAScript仕様書に書いていないとECMAScript準拠のブラウザが存在できなくなってしまう」などの意見が出ました。一時はdocument.allを説明するためのULEO (undefined-like exotic object) なる概念を導入する方向に進みましたが、これを仕様書に導入するとそのうち誰かがdocument.all以外のULEOという新たな黒歴史を生み出すかもしれないという懸念がありました。しかし、ECMAScriptの仕様書に「HTMLのdocument.allのみがULEOである」と書くのも何だか微妙ですね。

という感じであまり議論がまとまらない中、最終的に議論は2016年9月のTC39ミーティング(ECMAScriptの仕様に関する意思決定が行われるミーティングです)に持ち込まれました(議事録)。ここでもだいたい同じような議論が行われましたが、ULEOという名前はdocument.all以外にも使われそうなのでやっぱりやめようということになり、ここでHTMLDDAという名前が誕生しました。これはHTML Document Dot Allのことで、明らかにdocument.all専用という雰囲気が出ていますね。最終的には、前述のように[[IsHTMLDDA]]内部スロットがあるオブジェクトのみがこの挙動をすることになりました。要するに、ECMAScript仕様書の中にHTMLという概念が登場してしまうという部分は妥協しつつ、この変な挙動がさらに拡散をするのを防ぐ選択を取ったことになります。

ECMAScript仕様書にこの変更が反映されると、HTML仕様書のほうも書き直され、「document.allは(正確にはHTMLAllCollectionオブジェクトは)[[IsHTMLDDA]]を持つ」という定義に変更されました。めでたしめでたしですね。


まとめ

皆さんはいくつ誤解していましたか? 全部知ってたという方はおめでとうございます。ぜひTwitterとかはてなブックマークで自慢してください。(この記事へのリンクとあといいねを忘れずに!)