ECMAScript 2019の文法をコードで理解する


ECMAScript

概要

JavaScriptはECMAScriptというWeb標準仕様を実装されています。
本文書では主に、ECMAScriptを元にした解説を行います。

※「ECMAScript」を略して「ES」と表記する事があり、次のように省略されます。

正式名称 略称
ECMAScript 3 ES3
ECMAScript 6 ES6
ECMAScript 2019 ES2019

文法(Syntax)

ECMAScript2019では、ソースコード上の「最も構成となる要素」を3つに大別しています。
(※仕様を完全理解しているとは断言できない為、他にも要素がある可能性はあります。
詳細はECMAScript2019の仕様書を参照して下さい。)

構成要素 Syntaxの名前 配下のSyntax例
Statement ExpressionStatement(式文), VariableStatement(変数文), BlockStatement(ブロック)
宣言 Declaration FunctionDeclaration(関数宣言)
Expression AssignmentExpression(割当式), Expression(式)

あなたがソースコードを書く時、「文」と「宣言」はそのまま書いて実行する事が出来ます。

しかし、「式」だけを書いたソースコードの実行を試みると、SyntaxError (文法エラー)が発生します。
ソースコードは複数の「文」または「宣言」で構成されなければ、なりません。

「式」は「文」または「宣言」の中で決められた種類の「式」だけを記述可能なようにSyntaxが定められています。
詳細は ECMAScript 2019 の Syntax を追いかけて下さい。

関数宣言(FunctionDeclaration)

「関数宣言」は「宣言」に分類される為、関数宣言のみを書いても、SyntaxError (文法エラー)は発生しません。
JavaScript
function foo () {} // SyntaxError は発生しない

関数宣言は文法上、次の制約があります。

  • function キーワードで始まらなければならない(MUST)
  • 名前をつけなければならない(MUST)

前者は見た目通りなので分かると思いますが、後者は次のように「名前を省略して書くことが出来ない」という制約になります。

function () {} // SyntaxError: Function statements require a function name

上述のコメントは私が実行したWebブラウザ「Google Chrome 83.0.4103.61」のエラーメッセージです。
翻訳すると「文法エラー: 関数文には名前が必須です」となりますが、前節の通り、「関数宣言」は「宣言」の分類で「文」ではありませんので、「Function statements」を「Function declarations」に置き換えて解読する必要があります。

式文(Expression)

始めに私は、「式」は「文」または「宣言」の中で決められた種類の「式」だけを記述可能、と説明しました。
しかし、実は「式を単体で書いて動作する文」として「式文」が定義されています。

「式文」は「式」の後ろに ; (セミコロン)を置くだけのシンプルな構文です。
例えば、「後置インクリメント演算子(Postfix Increment Operator)」を「式文」で書くケースを良く見かけます。

i++;

i++ が「式」、i++; が「式文」です。
このコードは「文」として成立しているので、SyntaxError (文法エラー)は発生しません。

関数式(FunctionExpression)

「関数式」を「式文」で書いて見ましょう。

関数式は関数宣言と違い、名前を付けても付けなくても構わない、自由があります。
今回は、名前を付けた関数式(いわゆる「名前付き関数式」)の後ろに ;(セミコロン)を置いた「式文」を書いてみましょう。

function foo () {}; // SyntaxErrorは発生しない

エラーは発生しませんでしたが、ちょっと待ってください。

";" が存在しなければ、このコードは「関数宣言」となります。
もしも、「関数宣言」として実行されていたら、これは「式文」ではないことになります。

「関数式」は「関数宣言」と違い、関数名の名前を持つ変数を定義することはありません。
コンソールで変数 foo を出力できるのであれば、「関数宣言」として機能している事が証明されます。

console.log(foo); // foo () {}
function foo () {};

変数 foo が定義されているので、これは「関数宣言」のようです。
なぜ ; を後ろに書いたのに「式文」にならなかったのでしょうか。

実は、; には空文(EmptyStatement)という「何も実行しない文」が定義されています。

つまり、私が実行したWebブラウザ「Google Chrome 83.0.4103.61」は先のコードを次のように解釈していました。

console.log(foo);   // foo () {}
function foo () {}  // 関数宣言
;                   // 空文

このように「式文」とも「関数宣言」とも受け取れるコードを解析する場合の曖昧性を排除する為、ECMAScript2019では「式文」に特別な例外ルールを設けています。

NOTE: An ExpressionStatement cannot start with a U+007B (LEFT CURLY BRACKET) because that might make it ambiguous with a Block.
An ExpressionStatement cannot start with the function or class keywords because that would make it ambiguous with a FunctionDeclaration, a GeneratorDeclaration, or a ClassDeclaration.
An ExpressionStatement cannot start with async function because that would make it ambiguous with an AsyncFunctionDeclaration or a AsyncGeneratorDeclaration.
An ExpressionStatement cannot start with the two token sequence let [ because that would make it ambiguous with a let LexicalDeclaration whose first LexicalBinding was an ArrayBindingPattern.

以下、Google機械翻訳結果。

  • 注: ExpressionStatementをU + 007B(LEFT CURLY BRACKET)で開始することはできません。 これは、Blockがあいまいになる可能性があるためです。
  • ExpressionStatementをfunctionまたはclassキーワードで始めることはできません。 これは、FunctionDeclaration、GeneratorDeclaration、またはClassDeclarationがあいまいになるためです。
  • AsyncFunctionDeclarationまたはAsyncGeneratorDeclarationがあいまいになるため、ExpressionStatementを非同期関数で始めることはできません。 ExpressionStatementは、2つのトークンシーケンスlet [で始めることはできません。 これは、最初のLexicalBindingがArrayBindingPatternであるlet LexicalDeclarationで曖昧になるためです。」

「式文はfunctionキーワードで始められない」ので、先のコードが関数宣言として解釈される事は予め決まっていました。
では、functionキーワードで始まらない式に変更したら、どうでしょうか。

(function foo () {}); // SyntaxError は発生しない
foo;                  // ReferenceError: foo is not defined

() はグループ化演算子(Grouping Operator)で、演算子の優先順位を変更する為に使われます。
内部に一つだけ関数式を入れても優先順位は変わりませんが、functionキーワードで始まらないコードに変える事が出来ます。
ReferenceError: foo is not defined で変数 foo は存在しない事が証明された為、このコードは「関数宣言」ではありません。
期待通り、「式文」として動作させる事が出来ました。

ちなみに、このコードを少し変えてやると、いわゆる即時関数のコーディングパターンとなります。
(※ECMAScript 2019に「即時関数」の用語はありません。俗称です。)

/**
 * 名前付き関数式バージョン 
 **/
(function foo () {}()); // 即時実行する
(function foo () {})(); // 即時実行する

/**
 * 無名関数式バージョン 
 **/
(function () {}()); // 即時実行する
(function () {})(); // 即時実行する

よく知られるのはこの四種類ですが、「functionキーワードで始まらない」のルールさえ守れば良いので、他の演算子をfunctionキーワードの手前に置くことでも即時関数形式に出来ます。

/**
 * 名前付き関数式バージョン 
 **/
+function foo () {}(); // 即時実行する
-function foo () {}(); // 即時実行する
!function foo () {}(); // 即時実行する

/**
 * 無名関数式バージョン 
 **/
+function () {}(); // 即時実行する
-function () {}(); // 即時実行する
!function () {}(); // 即時実行する

これらは () と違い、「返り値」に変更を加えています。
従って、即時実行後の返り値に特別な演算をさせる用途において、活用できます。

歴史

MDNの歴史

「関数文」や「関数宣言文」のような「誤った表現」は古くからあり、MDNはその筆頭でした。

今では「関数宣言」と書かれていますが、2016/04/22時点では「function 文」と書かれていました。

前述の通り、「関数文」は仕様に存在しない独自用語なので、MDNのタイトルを「関数宣言」に修正したものと思われます。
ただし、URLは /Statement/ 配下のままで

上記ページタイトルを「文と宣言」に修正して辻褄を合わせたようです。
サブ見出しが「文と宣言(カテゴリ別)」とあるように、カテゴリ別にリストされていますが、「文」と「宣言」を区別できる分類にはなっていません。
例えば、「function」が「宣言」ではなく、「関数とクラス」に分類され、「var」が「宣言」に分類されるという矛盾が発生しています。
おそらく、タイトルが「文」の当時の分類のまま修正されずに残っているのでしょう。

ECMAScriptの歴史

var はES3からES2019まで VariableStatement (変数文)として定義されており、「文」に分類されます。

上記リンク先のES2019より一文を引用します。

A var statement declares variables that are scoped to the running execution context's VariableEnvironment.

Google機械翻訳結果「varステートメントは、実行中の実行コンテキストのVariableEnvironmentをスコープとする変数を宣言します。」

ようするに、「var文 = 変数宣言を行う文」であり、このあたりの説明が「変数文」ではなく「変数宣言」という構文であるかのように誤解される理由と思われます。
「変数宣言」という用語は「変数を宣言する」の動名詞となり、構文としての名前は「変数文」と記載するのが正解になります。


一方、関数宣言はES3当時からFunctionDeclaration(関数宣言)であり、「文」ではありませんでした。

ただし、ES3時点では「宣言(Declaration)」のSyntaxは存在せず、関数宣言の文法上の最上位は FunctionDeclaration でした。

ES6で初めて13章のタイトルが「Statements and Declarations」に変化し、Syntaxに Declaration (宣言)が定義されました。

Declaration :
  HoistableDeclaration
  ClassDeclaration

LexicalDeclaration :
  HoistableDeclaration
  FunctionDeclaration
  GeneratorDeclaration

HoistableDeclaration :
  FunctionDeclaration
  GeneratorDeclaration

class宣言(ClassDeclaration)、レキシカル宣言(LexicalDeclaration)が生まれた時に、関数宣言(FunctionDeclaration)のSyntaxも再定義されたようです。

しかし、変数文(VariableStatement)は再定義されなかったので、ES2019に至っても、変数系構文の分類が分かりにくい問題は継続しています。

構文名 分類 Syntax(ES2019)
変数文 VariableStatement
let宣言 宣言 LexicalDeclaration
const宣言 宣言 LexicalDeclaration