Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save think49/79d2aae159cfd2e61056e4b7d3615046 to your computer and use it in GitHub Desktop.
Save think49/79d2aae159cfd2e61056e4b7d3615046 to your computer and use it in GitHub Desktop.
ECMAScript 2019の文法をコードで理解する

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 (文法エラー)は発生しません。

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment