せっかちなプログラマーのためのJavaScript (ES2022版)
この本のサポートをお願いします: 購入 または 寄付
(広告です。ブロックしないでください。)

7章 構文



7.1 JavaScriptの構文の概要

これはJavaScriptの構文の最初の概要です. まだ理解できない部分があっても心配しないでください. この本の後半で詳しく説明します.

この概要は網羅的なものでもありません. 基本的なことに焦点を当てています.

7.1.1 基本的な構成要素

7.1.1.1 コメント
// single-line comment

/*
Comment with
multiple lines
*/
7.1.1.2 _プリミティブ_ (アトミック) 値

真偽値

true
false

数値

1.141
-123

基本的な数値型は、浮動小数点数(倍精度)と整数の両方に使用されます.

BigInt

17n
-49n

基本的な数値型は、符号付き53ビットの範囲内の整数のみを正しく表現できます. BigIntはサイズが任意に大きくなる可能性があります.

文字列

'abc'
"abc"
`String with interpolated values: ${256} and ${true}`

JavaScriptには文字専用の型がありません. 文字列を使用して文字を表します.

7.1.1.3 アサーション

_アサーション_は、計算結果がどのように見えるべきかを記述し、それらの期待が正しくない場合に例外をスローします. たとえば、次のアサーションは、計算7 + 1の結果が8でなければならないことを示しています.

assert.equal(7 + 1, 8);

assert.equal() は、2つの引数(実際の結果と予期される結果)を持つメソッド呼び出し(オブジェクトは assert、メソッドは .equal())です. これは、この本の後半で説明するNode.jsアサーションAPIの一部です.

オブジェクトを深く比較する assert.deepEqual() もあります.

7.1.1.4 コンソールへのログ出力

ブラウザまたはNode.jsのコンソールへのログ出力

// Printing a value to standard out (another method call)
console.log('Hello!');

// Printing error information to standard error
console.error('Something went wrong!');
7.1.1.5 演算子
// Operators for booleans
assert.equal(true && false, false); // And
assert.equal(true || false, true); // Or

// Operators for numbers
assert.equal(3 + 4, 7);
assert.equal(5 - 1, 4);
assert.equal(3 * 4, 12);
assert.equal(10 / 4, 2.5);

// Operators for bigints
assert.equal(3n + 4n, 7n);
assert.equal(5n - 1n, 4n);
assert.equal(3n * 4n, 12n);
assert.equal(10n / 4n, 2n);

// Operators for strings
assert.equal('a' + 'b', 'ab');
assert.equal('I see ' + 3 + ' monkeys', 'I see 3 monkeys');

// Comparison operators
assert.equal(3 < 4, true);
assert.equal(3 <= 4, true);
assert.equal('abc' === 'abc', true);
assert.equal('abc' !== 'def', true);

JavaScriptには、==比較演算子もあります. これを避けることをお勧めします. その理由は、§13.4.3「推奨事項: 常に厳密等価性を使用する」で説明されています.

7.1.1.6 変数の宣言

const は_不変の変数バインディング_を作成します. 各変数はすぐに初期化する必要があり、後で別の値を割り当てることはできません. ただし、値自体は変更可能であり、その内容を変更できる場合があります. つまり、const は値を不変にするわけではありません.

// Declaring and initializing x (immutable binding):
const x = 8;

// Would cause a TypeError:
// x = 9;

let は_可変の変数バインディング_を作成します.

// Declaring y (mutable binding):
let y;

// We can assign a different value to y:
y = 3 * 5;

// Declaring and initializing z:
let z = 3 * 5;
7.1.1.7 通常の関数宣言
// add1() has the parameters a and b
function add1(a, b) {
  return a + b;
}
// Calling function add1()
assert.equal(add1(5, 2), 7);
7.1.1.8 アロー関数式

アロー関数式は、特に関数呼び出しとメソッド呼び出しの引数として使用されます.

const add2 = (a, b) => { return a + b };
// Calling function add2()
assert.equal(add2(5, 2), 7);

// Equivalent to add2:
const add3 = (a, b) => a + b;

前のコードには、次の2つのアロー関数が含まれています(_式_と_文_という用語は、この章の後半で説明します).

// An arrow function whose body is a code block
(a, b) => { return a + b }

// An arrow function whose body is an expression
(a, b) => a + b
7.1.1.9 プレーンオブジェクト
// Creating a plain object via an object literal
const obj = {
  first: 'Jane', // property
  last: 'Doe', // property
  getFullName() { // property (method)
    return this.first + ' ' + this.last;
  },
};

// Getting a property value
assert.equal(obj.first, 'Jane');
// Setting a property value
obj.first = 'Janey';

// Calling the method
assert.equal(obj.getFullName(), 'Janey Doe');
7.1.1.10 配列
// Creating an Array via an Array literal
const arr = ['a', 'b', 'c'];
assert.equal(arr.length, 3);

// Getting an Array element
assert.equal(arr[1], 'b');
// Setting an Array element
arr[1] = 'β';

// Adding an element to an Array:
arr.push('d');

assert.deepEqual(
  arr, ['a', 'β', 'c', 'd']);
7.1.1.11 制御フロー文

条件文

if (x < 0) {
  x = -x;
}

for-of ループ

const arr = ['a', 'b'];
for (const element of arr) {
  console.log(element);
}
// Output:
// 'a'
// 'b'

7.1.2 モジュール

各モジュールは単一のファイルです. たとえば、モジュールを含む次の2つのファイルを考えてみましょう.

file-tools.mjs
main.mjs

file-tools.mjs のモジュールは、関数 isTextFilePath() をエクスポートします.

export function isTextFilePath(filePath) {
  return filePath.endsWith('.txt');
}

main.mjs のモジュールは、モジュール path 全体と関数 isTextFilePath() をインポートします.

// Import whole module as namespace object `path`
import * as path from 'path';
// Import a single export of module file-tools.mjs
import {isTextFilePath} from './file-tools.mjs';

7.1.3 クラス

class Person {
  constructor(name) {
    this.name = name;
  }
  describe() {
    return `Person named ${this.name}`;
  }
  static logNames(persons) {
    for (const person of persons) {
      console.log(person.name);
    }
  }
}

class Employee extends Person {
  constructor(name, title) {
    super(name);
    this.title = title;
  }
  describe() {
    return super.describe() +
      ` (${this.title})`;
  }
}

const jane = new Employee('Jane', 'CTO');
assert.equal(
  jane.describe(),
  'Person named Jane (CTO)');

7.1.4 例外処理

function throwsException() {
  throw new Error('Problem!');
}

function catchesException() {
  try {
    throwsException();
  } catch (err) {
    assert.ok(err instanceof Error);
    assert.equal(err.message, 'Problem!');
  }
}

注記

変数名とプロパティ名の文法カテゴリは、_識別子_と呼ばれます.

識別子には、次の文字を使用できます.

JavaScriptでは特別な意味を持つ単語があり、_予約語_と呼ばれます. 例としては、iftrueconst などがあります.

予約語は変数名として使用できません.

const if = 123;
  // SyntaxError: Unexpected token if

ただし、プロパティの名前として使用することはできます.

> const obj = { if: 123 };
> obj.if
123

7.1.6 大文字と小文字のスタイル

単語を連結するための一般的な大文字と小文字のスタイルは次のとおりです.

7.1.7 名前の大文字化

一般に、JavaScriptは定数を除いてキャメルケースを使用します.

小文字

大文字

7.1.8 その他の命名規則

次の命名規則はJavaScriptでよく使用されます.

パラメータの名前がアンダースコアで始まる(またはアンダースコアである)場合、そのパラメータは使用されていないことを意味します. 例:

arr.map((_x, i) => i)

オブジェクトのプロパティの名前がアンダースコアで始まる場合、そのプロパティはプライベートと見なされます.

class ValueWrapper {
  constructor(value) {
    this._value = value;
  }
}

7.1.9 セミコロンはどこに置くべきか?

文の末尾

const x = 123;
func();

ただし、その文が中括弧で終わる場合は除きます.

while (false) {
  // ···
} // no semicolon

function func() {
  // ···
} // no semicolon

ただし、そのような文の後にセミコロンを追加しても構文エラーにはなりません. これは空の文として解釈されます.

// Function declaration followed by empty statement:
function func() {
  // ···
};

  クイズ: 基本

クイズアプリをご覧ください.

7.2 (上級)

この章の残りのセクションはすべて上級者向けです.

7.3 識別子

7.3.1 有効な識別子(変数名など)

先頭文字

後続文字

const ε = 0.0001;
const строка = '';
let _tmp = 0;
const $foo2 = true;

7.3.2 予約語

予約語は変数名にすることはできませんが、プロパティ名にすることはできます.

すべてのJavaScript _キーワード_は予約語です:

await break case catch class const continue debugger default delete do else export extends finally for function if import in instanceof let new return static super switch this throw try typeof var void while with yield

次のトークンもキーワードですが、現在言語では使用されていません.

enum implements package protected interface private public

次のリテラルは予約語です.

true false null

技術的には、これらの単語は予約されていませんが、事実上キーワードであるため、避ける必要があります.

Infinity NaN undefined async

グローバル変数(StringMath など)の名前を独自の変数やパラメータに使用しないでください.

7.4 文と式

このセクションでは、JavaScriptが2種類の構文構造(_文_と_式_)をどのように区別するかを探ります. その後、同じ構文が、使用される場所に応じて異なる意味を持つ可能性があるため、問題が発生する可能性があることがわかります.

  文と式のみがあると仮定します

簡単にするために、JavaScriptには文と式のみがあると仮定します.

7.4.1 文

_文_とは、実行可能で何らかのアクションを実行するコードの一部です. たとえば、if は文です.

let myStr;
if (myBool) {
  myStr = 'Yes';
} else {
  myStr = 'No';
}

文のもう1つの例: 関数宣言.

function twice(x) {
  return x + x;
}

7.4.2 式

_式_とは、値を生成するために_評価_できるコードの一部です. たとえば、括弧で囲まれたコードは式です.

let myStr = (myBool ? 'Yes' : 'No');

括弧の間で使用される演算子 _?_:_ は、_三項演算子_と呼ばれます. これは、if 文の式バージョンです.

式の例をさらに見てみましょう. 式を入力すると、REPLがそれを評価します.

> 'ab' + 'cd'
'abcd'
> Number('123')
123
> true || false
true

7.4.3 どこに何が許されるか?

JavaScriptのソースコード内における現在の位置によって、使用できる構文の種類が決まります。

ただし、式は文として使用できます。その場合、それらは式文と呼ばれます。逆は真ではありません。コンテキストで式が必要な場合、文を使用することはできません。

次のコードは、任意の式bar()が式または文のいずれかになることを示しています。これはコンテキストによって異なります。

function f() {
  console.log(bar()); // bar() is expression
  bar(); // bar(); is (expression) statement  
}

7.5 曖昧な構文

JavaScriptには、構文的に曖昧なプログラミング構成要素がいくつかあります。同じ構文が、文コンテキストで使用されるか式コンテキストで使用されるかによって、異なる解釈がされます。このセクションでは、この現象とその落とし穴について探ります。

7.5.1 同じ構文: 関数宣言と関数式

関数宣言は文です。

function id(x) {
  return x;
}

関数式は式です(=の右辺)。

const id = function me(x) {
  return x;
};

7.5.2 同じ構文: オブジェクトリテラルとブロック

次のコードでは、{}オブジェクトリテラルです。空のオブジェクトを作成する式です。

const obj = {};

これは空のコードブロック(文)です。

{
}

7.5.3 曖昧性の解消

曖昧さは、文コンテキストでのみ問題になります。JavaScriptパーサーが曖昧な構文に遭遇した場合、それが単純な文なのか式文なのかがわかりません。例えば

曖昧さを解決するために、functionまたは{で始まる文は、式として解釈されることはありません。式文をこれらのトークンのいずれかで開始する場合は、括弧で囲む必要があります。

(function (x) { console.log(x) })('abc');

// Output:
// 'abc'

このコードでは

  1. まず、関数式を使用して関数を作成します。

    function (x) { console.log(x) }
  2. 次に、その関数を呼び出します: ('abc')

(1)に示されているコードフラグメントは、括弧で囲まれているため、式としてのみ解釈されます。括弧で囲まないと、JavaScriptは関数宣言を期待し、関数名がないことを訴えるため、構文エラーが発生します。さらに、関数宣言の直後に関数呼び出しを配置することはできません。

本書の後半では、構文の曖昧さが原因で発生する落とし穴の例をさらに紹介します。

7.6 セミコロン

7.6.1 セミコロンの経験則

各文はセミコロンで終了します。

const x = 3;
someFunction('abc');
i++;

ただし、ブロックで終わる文は除きます。

function foo() {
  // ···
}
if (y > 0) {
  // ···
}

次のケースは少しトリッキーです。

const func = () => {}; // semicolon!

const宣言全体(文)はセミコロンで終わりますが、その中にはアロー関数式があります。つまり、中括弧で終わるのは文自体ではなく、埋め込まれたアロー関数式です。そのため、最後にセミコロンがあります。

7.6.2 セミコロン: 制御文

制御文の本体は、それ自体が文です。たとえば、これはwhileループの構文です。

while (condition)
  statement

本体は単一の文にすることができます。

while (a > 0) a--;

しかし、ブロックも文であるため、制御文の有効な本体となります。

while (a > 0) {
  a--;
}

ループの本体を空にする場合は、最初の選択肢は空の文(セミコロンのみ)です。

while (processNextItem() > 0);

2番目の選択肢は空のブロックです。

while (processNextItem() > 0) {}

7.7 自動セミコロン挿入 (ASI)

常にセミコロンを記述することをお勧めしますが、JavaScriptではほとんどのセミコロンは省略可能です。これを可能にするメカニズムは、自動セミコロン挿入 (ASI) と呼ばれます。ある意味で、構文エラーを修正します。

ASIは次のように動作します。文の解析は、次のいずれかになるまで続行されます。

言い換えれば、ASIは改行時にセミコロンを挿入するものと見なすことができます。次のサブセクションでは、ASIの落とし穴について説明します。

7.7.1 ASIが予期せずトリガーされる

ASIの良い点は、ASIに頼らず常にセミコロンを記述する場合、注意が必要な落とし穴が1つだけあることです。それは、JavaScriptが一部のトークンの後に改行を禁止していることです。改行を挿入すると、セミコロンも挿入されます。

これが最も実際に関連するトークンは、returnです。たとえば、次のコードを考えてみましょう。

return
{
  first: 'jane'
};

このコードは次のように解析されます。

return;
{
  first: 'jane';
}
;

つまり

JavaScriptがなぜこれを行うのでしょうか? returnの後の行で誤って値を返さないようにするためです。

7.7.2 ASIが予期せずトリガーされない

場合によっては、ASIがトリガーされるべきと考えられる場合でも、トリガーされ*ない*ことがあります。これは、セミコロンを好まない人にとっては、これらのケースを認識する必要があるため、生活をより複雑にします。以下に3つの例を示します。他にもあります。

例1: 意図しない関数呼び出し。

a = b + c
(d + e).print()

次のように解析されます。

a = b + c(d + e).print();

例2: 意図しない除算。

a = b
/hi/g.exec(c).map(d)

次のように解析されます。

a = b / hi / g.exec(c).map(d);

例3: 意図しないプロパティアクセス。

someFunction()
['ul', 'ol'].map(x => x + x)

次のように実行されます。

const propKey = ('ul','ol'); // comma operator
assert.equal(propKey, 'ol');

someFunction()[propKey].map(x => x + x);

7.8 セミコロン: ベストプラクティス

常にセミコロンを記述することをお勧めします。

ただし、セミコロンによる視覚的な煩雑さを好まない人も多くいます。あなたがその一人である場合: セミコロンなしのコードは*合法*です。間違いを避けるためにツールを使用することをお勧めします。以下に2つの例を示します。

7.9 厳格モードと非厳格モード

ECMAScript 5以降、JavaScriptには、JavaScriptを実行できる2つの*モード*があります。

最新のJavaScriptコードでは、ほとんどの場合モジュール内に配置されているため、非厳格モードに遭遇することはめったにありません。本書では、厳格モードが常にオンになっていると想定しています。

7.9.1 厳格モードの切り替え

スクリプトファイルとCommonJSモジュールでは、最初の行に次のコードを配置することで、ファイル全体に対して厳格モードをオンにします。

'use strict';

この「ディレクティブ」の優れた点は、ECMAScriptバージョン5より前のバージョンでは単に無視されることです。これは、何もしない式文です。

単一の関数に対してのみ厳格モードをオンにすることもできます。

function functionInStrictMode() {
  'use strict';
}

7.9.2 厳格モードの改善

厳格モードが非厳格モードよりも優れている3つの点を見てみましょう。このセクションのみでは、すべてのコードフラグメントは非厳格モードで実行されます。

7.9.2.1 非厳格モードの落とし穴: 宣言されていない変数を変更するとグローバル変数が作成される

非厳格モードでは、宣言されていない変数を変更するとグローバル変数が作成されます。

function sloppyFunc() {
  undeclaredVar1 = 123;
}
sloppyFunc();
// Created global variable `undeclaredVar1`:
assert.equal(undeclaredVar1, 123);

厳格モードでは、ReferenceErrorがスローされるため、より適切に処理されます。これにより、タイプミスを検出しやすくなります。

function strictFunc() {
  'use strict';
  undeclaredVar2 = 123;
}
assert.throws(
  () => strictFunc(),
  {
    name: 'ReferenceError',
    message: 'undeclaredVar2 is not defined',
  });

assert.throws()は、最初の引数である関数が呼び出されるとReferenceErrorをスローすることを示しています。

7.9.2.2 関数宣言は、厳格モードではブロックスコープ、非厳格モードでは関数スコープ

厳格モードでは、関数宣言によって作成された変数は、最も内側のブロック内でのみ存在します。

function strictFunc() {
  'use strict';
  {
    function foo() { return 123 }
  }
  return foo(); // ReferenceError
}
assert.throws(
  () => strictFunc(),
  {
    name: 'ReferenceError',
    message: 'foo is not defined',
  });

非厳格モードでは、関数宣言は関数スコープです。

function sloppyFunc() {
  {
    function foo() { return 123 }
  }
  return foo(); // works
}
assert.equal(sloppyFunc(), 123);
7.9.2.3 非厳格モードでは、不変データを変更しても例外がスローされない

厳格モードでは、不変データを変更しようとすると例外が発生します。

function strictFunc() {
  'use strict';
  true.prop = 1; // TypeError
}
assert.throws(
  () => strictFunc(),
  {
    name: 'TypeError',
    message: "Cannot create property 'prop' on boolean 'true'",
  });

非厳格モードでは、代入はサイレントに失敗します。

function sloppyFunc() {
  true.prop = 1; // fails silently
  return true.prop;
}
assert.equal(sloppyFunc(), undefined);

  参考文献: 非厳格モード

非厳格モードと厳格モードの違いの詳細については、MDNを参照してください。

  クイズ: 上級

クイズアプリをご覧ください.